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

Merge pull request #19848 from MetaMask/Version-v10.34.0

Version v10.34.0 RC
This commit is contained in:
Dan J Miller 2023-07-14 15:06:12 -02:30 committed by GitHub
commit 839543c5db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
388 changed files with 13533 additions and 18598 deletions

View File

@ -30,6 +30,45 @@ rc_branch_only: &rc_branch_only
only:
- /^Version-v(\d+)[.](\d+)[.](\d+)/
aliases:
# Shallow Git Clone
- &shallow-git-clone
name: Shallow Git Clone
command: |
#!/bin/bash
set -e
set -u
set -o pipefail
# This Shallow Git Clone code is adapted from what the standard CircleCI `checkout` command does for the case of an external PR (link to example below):
# https://app.circleci.com/pipelines/github/MetaMask/metamask-extension/49817/workflows/dc195ea6-ac06-4de1-9edf-4c949427b5fb/jobs/1430976/parallel-runs/0/steps/0-101
### git clone --no-checkout "$CIRCLE_REPOSITORY_URL" .
### git fetch --force origin +refs/pull/18748/head:refs/remotes/origin/pull/18748
### git checkout --force -B "$CIRCLE_BRANCH" "$CIRCLE_SHA1"
### git --no-pager log --no-color -n 1 --format='HEAD is now at %h %s'
# Set up SSH access
# This SSH key is the current github.com SSH key as of June 2023, but it will need to be changed whenever github changes their key (probably every few years)
GITHUB_SSH_KEY="AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl"
mkdir -p ~/.ssh
echo github.com ssh-ed25519 $GITHUB_SSH_KEY >> ~/.ssh/known_hosts
# Take a different clone path depending on if it's a tag, a PR from an external repo, or the normal case
if [ -n "${CIRCLE_TAG-}" ]; then
# tag
git clone --depth 1 --no-checkout "$CIRCLE_REPOSITORY_URL" .
git fetch --depth 1 --force origin "+refs/tags/${CIRCLE_TAG}:refs/tags/${CIRCLE_TAG}"
git checkout --force -q "$CIRCLE_TAG" "$CIRCLE_SHA1"
elif [[ "$CIRCLE_BRANCH" =~ ^pull\/* ]]; then
# pull request
git clone --depth 1 --no-checkout "$CIRCLE_REPOSITORY_URL" .
git fetch --depth 1 --force origin "${CIRCLE_BRANCH}/head:remotes/origin/${CIRCLE_BRANCH}"
git checkout --force -B "$CIRCLE_BRANCH" "$CIRCLE_SHA1"
else
# normal case
git clone --depth 1 "$CIRCLE_REPOSITORY_URL" --branch "$CIRCLE_BRANCH" .
fi
workflows:
test_and_release:
jobs:
@ -50,11 +89,16 @@ workflows:
- test-yarn-dedupe:
requires:
- prep-deps
- validate-lavamoat-config:
filters:
branches:
only:
- /^Version-v(\d+)[.](\d+)[.](\d+)|master/
- validate-lavamoat-allow-scripts:
requires:
- prep-deps
- validate-lavamoat-policy-build:
requires:
- prep-deps
- validate-lavamoat-policy-webapp:
matrix:
parameters:
build-type: [main, beta, flask, mmi, desktop]
requires:
- prep-deps
- prep-build:
@ -162,7 +206,9 @@ workflows:
- prep-build-flask
- all-tests-pass:
requires:
- validate-lavamoat-config
- validate-lavamoat-allow-scripts
- validate-lavamoat-policy-build
- validate-lavamoat-policy-webapp
- test-lint
- test-lint-shellcheck
- test-lint-lockfile
@ -234,7 +280,7 @@ jobs:
trigger-beta-build:
executor: node-browsers-medium-plus
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- when:
@ -263,7 +309,7 @@ jobs:
create_release_pull_request:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -282,7 +328,7 @@ jobs:
prep-deps:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- restore_cache:
keys:
# First try to get the specific cache for the checksum of the yarn.lock file.
@ -329,25 +375,52 @@ jobs:
- node_modules
- build-artifacts
validate-lavamoat-config:
validate-lavamoat-allow-scripts:
executor: node-browsers-medium-plus
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
name: Validate allow-scripts config
command: |
.circleci/scripts/validate-allow-scripts.sh
command: yarn allow-scripts auto
- run:
name: Validate LavaMoat policy
command: |
.circleci/scripts/validate-lavamoat-policy.sh
name: Check working tree
command: .circleci/scripts/check-working-tree.sh
validate-lavamoat-policy-build:
executor: node-browsers-medium-plus
steps:
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
name: Validate LavaMoat build policy
command: yarn lavamoat:build:auto
- run:
name: Check working tree
command: .circleci/scripts/check-working-tree.sh
validate-lavamoat-policy-webapp:
executor: node-browsers-medium-plus
parameters:
build-type:
type: string
steps:
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
name: Validate LavaMoat << parameters.build-type >> policy
command: yarn lavamoat:webapp:auto:ci '--build-types=<< parameters.build-type >>'
- run:
name: Check working tree
command: .circleci/scripts/check-working-tree.sh
prep-build:
executor: node-browsers-medium-plus
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- when:
@ -381,7 +454,7 @@ jobs:
prep-build-desktop:
executor: node-browsers-medium-plus
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -405,7 +478,7 @@ jobs:
prep-build-flask:
executor: node-browsers-medium-plus
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- when:
@ -445,7 +518,7 @@ jobs:
prep-build-test-flask:
executor: node-browsers-medium-plus
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -466,7 +539,7 @@ jobs:
prep-build-test-mv3:
executor: node-browsers-medium-plus
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -487,7 +560,7 @@ jobs:
prep-build-test:
executor: node-browsers-medium-plus
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -508,7 +581,7 @@ jobs:
prep-build-storybook:
executor: node-browsers-large
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -522,7 +595,7 @@ jobs:
prep-build-ts-migration-dashboard:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -536,7 +609,7 @@ jobs:
test-yarn-dedupe:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -546,7 +619,7 @@ jobs:
test-lint:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -559,7 +632,7 @@ jobs:
test-storybook:
executor: node-browsers-large
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -581,7 +654,7 @@ jobs:
test-lint-lockfile:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -591,7 +664,7 @@ jobs:
test-lint-changelog:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- when:
@ -617,7 +690,7 @@ jobs:
test-deps-audit:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -627,7 +700,7 @@ jobs:
test-deps-depcheck:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -638,7 +711,7 @@ jobs:
executor: node-browsers
parallelism: 8
steps:
- checkout
- run: *shallow-git-clone
- run:
name: Re-Install Chrome
command: ./.circleci/scripts/chrome-install.sh
@ -675,7 +748,7 @@ jobs:
executor: node-browsers
parallelism: 8
steps:
- checkout
- run: *shallow-git-clone
- run:
name: Re-Install Chrome
command: ./.circleci/scripts/chrome-install.sh
@ -703,7 +776,7 @@ jobs:
executor: node-browsers
parallelism: 4
steps:
- checkout
- run: *shallow-git-clone
- run:
name: Install Firefox
command: ./.circleci/scripts/firefox-install.sh
@ -740,7 +813,7 @@ jobs:
executor: node-browsers
parallelism: 4
steps:
- checkout
- run: *shallow-git-clone
- run:
name: Re-Install Chrome
command: ./.circleci/scripts/chrome-install.sh
@ -777,7 +850,7 @@ jobs:
executor: node-browsers-medium-plus
parallelism: 8
steps:
- checkout
- run: *shallow-git-clone
- run:
name: Install Firefox
command: ./.circleci/scripts/firefox-install.sh
@ -813,7 +886,7 @@ jobs:
benchmark:
executor: node-browsers-medium-plus
steps:
- checkout
- run: *shallow-git-clone
- run:
name: Re-Install Chrome
command: ./.circleci/scripts/chrome-install.sh
@ -839,7 +912,7 @@ jobs:
user-actions-benchmark:
executor: node-browsers-medium-plus
steps:
- checkout
- run: *shallow-git-clone
- run:
name: Re-Install Chrome
command: ./.circleci/scripts/chrome-install.sh
@ -865,7 +938,7 @@ jobs:
stats-module-load-init:
executor: node-browsers-medium-plus
steps:
- checkout
- run: *shallow-git-clone
- run:
name: Re-Install Chrome
command: ./.circleci/scripts/chrome-install.sh
@ -962,7 +1035,7 @@ jobs:
job-publish-release:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -982,7 +1055,7 @@ jobs:
- add_ssh_keys:
fingerprints:
- '3d:49:29:f4:b2:e8:ea:af:d1:32:eb:2a:fc:15:85:d8'
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -997,7 +1070,7 @@ jobs:
- add_ssh_keys:
fingerprints:
- '8b:21:e3:20:7c:c9:db:82:74:2d:86:d6:11:a7:2f:49'
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -1011,7 +1084,7 @@ jobs:
test-unit-mocha:
executor: node-browsers-medium-plus
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -1026,7 +1099,7 @@ jobs:
test-unit-jest-development:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -1043,7 +1116,7 @@ jobs:
executor: node-browsers-medium-plus
parallelism: 12
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -1059,7 +1132,7 @@ jobs:
upload-and-validate-coverage:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- codecov/upload
@ -1074,7 +1147,7 @@ jobs:
test-unit-global:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -1084,7 +1157,7 @@ jobs:
validate-source-maps:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -1094,7 +1167,7 @@ jobs:
validate-source-maps-beta:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -1105,7 +1178,7 @@ jobs:
validate-source-maps-desktop:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -1121,7 +1194,7 @@ jobs:
validate-source-maps-flask:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -1137,7 +1210,7 @@ jobs:
test-mozilla-lint:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -1147,7 +1220,7 @@ jobs:
test-mozilla-lint-desktop:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:
@ -1163,7 +1236,7 @@ jobs:
test-mozilla-lint-flask:
executor: node-browsers
steps:
- checkout
- run: *shallow-git-clone
- attach_workspace:
at: .
- run:

View File

@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -e
set -u
set -o pipefail
if ! git diff --exit-code
then
echo "Working tree dirty"
exit 1
fi

View File

@ -7,12 +7,12 @@ 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_VERSION='114.0.5735.133-1'
CHROME_BINARY="google-chrome-stable_${CHROME_VERSION}_amd64.deb"
CHROME_BINARY_URL="https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/${CHROME_BINARY}"
# To retrieve this checksum, run the `wget` and `shasum` commands below
CHROME_BINARY_SHA512SUM='bbfd436c17d6f0554b91211ecf1324aeeac012f1d000d610f93956dbfb8387c0adb56f921c5b7bcc1833c49ab2abbd3bbc250001f650b3ca4f79cebe708c29ae'
CHROME_BINARY_SHA512SUM='0b1a18c44efb72ed3e69a5f78419ff5fa973df42b18a8becfcc3d4f6825957c637e9396d07756f910f2d9c7c85a3e2b64cc30cca18182ae8811feadd609f159d'
wget -O "${CHROME_BINARY}" -t 5 "${CHROME_BINARY_URL}"

View File

@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -e
set -u
set -o pipefail
yarn allow-scripts auto
if git diff --exit-code
then
echo "allow-scripts configuration is up-to-date"
else
echo "allow-scripts configuration requires updates"
exit 1
fi

View File

@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -e
set -u
set -o pipefail
yarn lavamoat:auto:ci
if git diff --exit-code
then
echo "LavaMoat policy is up-to-date"
else
echo "LavaMoat policy requires updates"
exit 1
fi

View File

@ -238,7 +238,6 @@ module.exports = {
excludedFiles: [
'app/scripts/controllers/app-state.test.js',
'app/scripts/controllers/mmi-controller.test.js',
'app/scripts/controllers/network/**/*.test.js',
'app/scripts/controllers/permissions/**/*.test.js',
'app/scripts/lib/**/*.test.js',
'app/scripts/migrations/*.test.js',
@ -267,9 +266,6 @@ module.exports = {
'**/__snapshots__/*.snap',
'app/scripts/controllers/app-state.test.js',
'app/scripts/controllers/mmi-controller.test.js',
'app/scripts/controllers/network/**/*.test.js',
'app/scripts/controllers/network/**/*.test.ts',
'app/scripts/controllers/network/provider-api-tests/*.ts',
'app/scripts/controllers/permissions/**/*.test.js',
'app/scripts/lib/**/*.test.js',
'app/scripts/migrations/*.test.js',

View File

@ -0,0 +1,328 @@
import * as core from '@actions/core';
import { context, getOctokit } from '@actions/github';
import { GitHub } from '@actions/github/lib/utils';
// A labelable object can be a pull request or an issue
interface Labelable {
id: string;
number: number;
repoOwner: string;
repoName: string;
createdAt: string;
}
main().catch((error: Error): void => {
console.error(error);
process.exit(1);
});
async function main(): Promise<void> {
// "GITHUB_TOKEN" is an automatically generated, repository-specific access token provided by GitHub Actions.
// We can't use "GITHUB_TOKEN" here, as its permissions are scoped to the repository where the action is running.
// "GITHUB_TOKEN" does not have access to other repositories, even when they belong to the same organization.
// As we want to update linked issues which are not necessarily located in the same repository,
// we need to create our own "RELEASE_LABEL_TOKEN" with "repo" permissions.
// Such a token allows to access other repositories of the MetaMask organisation.
const personalAccessToken = process.env.RELEASE_LABEL_TOKEN;
if (!personalAccessToken) {
core.setFailed('RELEASE_LABEL_TOKEN not found');
process.exit(1);
}
const nextReleaseVersionNumber = process.env.NEXT_SEMVER_VERSION;
if (!nextReleaseVersionNumber) {
// NEXT_SEMVER_VERSION is automatically deduced as minor version bump on top of the latest version
// found, either in repo's list of branches, or in repo's list of tags or in repo's "package.json" version.
// For edge cases (e.g. major version bumps, etc.), where the value can not be deduced automatically,
// NEXT_SEMVER_VERSION can be defined manually set by defining FORCE_NEXT_SEMVER_VERSION variable in
// section "Secrets and variables">"Actions">"Variables">"New repository variable" in the settings of this repo.
// Example value: 6.5.0
core.setFailed('NEXT_SEMVER_VERSION not found');
process.exit(1);
}
if (!isValidVersionFormat(nextReleaseVersionNumber)) {
core.setFailed(`NEXT_SEMVER_VERSION (${nextReleaseVersionNumber}) is not a valid version format. The expected format is "x.y.z", where "x", "y" and "z" are numbers.`);
process.exit(1);
}
// Release label indicates the next release version number
// Example release label: "release-6.5.0"
const releaseLabelName = `release-${nextReleaseVersionNumber}`;
const releaseLabelColor = "ededed";
const releaseLabelDescription = `Issue or pull request that will be included in release ${nextReleaseVersionNumber}`;
// Initialise octokit, required to call Github GraphQL API
const octokit: InstanceType<typeof GitHub> = getOctokit(
personalAccessToken,
{
previews: ["bane"], // The "bane" preview is required for adding, updating, creating and deleting labels.
},
);
// Retrieve pull request info from context
const prRepoOwner = context.repo.owner;
const prRepoName = context.repo.repo;
const prNumber = context.payload.pull_request?.number;
if (!prNumber) {
core.setFailed('Pull request number not found');
process.exit(1);
}
// Retrieve pull request
const pullRequest: Labelable = await retrievePullRequest(octokit, prRepoOwner, prRepoName, prNumber);
// Add the release label to the pull request
await addLabelToLabelable(octokit, pullRequest, releaseLabelName, releaseLabelColor, releaseLabelDescription);
// Retrieve linked issues for the pull request
const linkedIssues: Labelable[] = await retrieveLinkedIssues(octokit, prRepoOwner, prRepoName, prNumber);
// Add the release label to the linked issues
for (const linkedIssue of linkedIssues) {
await addLabelToLabelable(octokit, linkedIssue, releaseLabelName, releaseLabelColor, releaseLabelDescription);
}
}
// This helper function checks if version has the correct format: "x.y.z" where "x", "y" and "z" are numbers.
function isValidVersionFormat(str: string): boolean {
const regex = /^\d+\.\d+\.\d+$/;
return regex.test(str);
}
// This function retrieves the repo
async function retrieveRepo(octokit: InstanceType<typeof GitHub>, repoOwner: string, repoName: string): Promise<string> {
const retrieveRepoQuery = `
query RetrieveRepo($repoOwner: String!, $repoName: String!) {
repository(owner: $repoOwner, name: $repoName) {
id
}
}
`;
const retrieveRepoResult: {
repository: {
id: string;
};
} = await octokit.graphql(retrieveRepoQuery, {
repoOwner,
repoName,
});
const repoId = retrieveRepoResult?.repository?.id;
return repoId;
}
// This function retrieves the label on a specific repo
async function retrieveLabel(octokit: InstanceType<typeof GitHub>, repoOwner: string, repoName: string, labelName: string): Promise<string> {
const retrieveLabelQuery = `
query RetrieveLabel($repoOwner: String!, $repoName: String!, $labelName: String!) {
repository(owner: $repoOwner, name: $repoName) {
label(name: $labelName) {
id
}
}
}
`;
const retrieveLabelResult: {
repository: {
label: {
id: string;
};
};
} = await octokit.graphql(retrieveLabelQuery, {
repoOwner,
repoName,
labelName,
});
const labelId = retrieveLabelResult?.repository?.label?.id;
return labelId;
}
// This function creates the label on a specific repo
async function createLabel(octokit: InstanceType<typeof GitHub>, repoId: string, labelName: string, labelColor: string, labelDescription: string): Promise<string> {
const createLabelMutation = `
mutation CreateLabel($repoId: ID!, $labelName: String!, $labelColor: String!, $labelDescription: String) {
createLabel(input: {repositoryId: $repoId, name: $labelName, color: $labelColor, description: $labelDescription}) {
label {
id
}
}
}
`;
const createLabelResult: {
createLabel: {
label: {
id: string;
};
};
} = await octokit.graphql(createLabelMutation, {
repoId,
labelName,
labelColor,
labelDescription,
});
const labelId = createLabelResult?.createLabel?.label?.id;
return labelId;
}
// This function creates or retrieves the label on a specific repo
async function createOrRetrieveLabel(octokit: InstanceType<typeof GitHub>, repoOwner: string, repoName: string, labelName: string, labelColor: string, labelDescription: string): Promise<string> {
// Check if label already exists on the repo
let labelId = await retrieveLabel(octokit, repoOwner, repoName, labelName);
// If label doesn't exist on the repo, create it
if (!labelId) {
// Retrieve PR's repo
const repoId = await retrieveRepo(octokit, repoOwner, repoName);
// Create label on repo
labelId = await createLabel(octokit, repoId, labelName, labelColor, labelDescription);
}
return labelId;
}
// This function retrieves the pull request on a specific repo
async function retrievePullRequest(octokit: InstanceType<typeof GitHub>, repoOwner: string, repoName: string, prNumber: number): Promise<Labelable> {
const retrievePullRequestQuery = `
query GetPullRequest($repoOwner: String!, $repoName: String!, $prNumber: Int!) {
repository(owner: $repoOwner, name: $repoName) {
pullRequest(number: $prNumber) {
id
createdAt
}
}
}
`;
const retrievePullRequestResult: {
repository: {
pullRequest: {
id: string;
createdAt: string;
};
};
} = await octokit.graphql(retrievePullRequestQuery, {
repoOwner,
repoName,
prNumber,
});
const pullRequest: Labelable = {
id: retrievePullRequestResult?.repository?.pullRequest?.id,
number: prNumber,
repoOwner: repoOwner,
repoName: repoName,
createdAt: retrievePullRequestResult?.repository?.pullRequest?.createdAt,
}
return pullRequest;
}
// This function retrieves the list of linked issues for a pull request
async function retrieveLinkedIssues(octokit: InstanceType<typeof GitHub>, repoOwner: string, repoName: string, prNumber: number): Promise<Labelable[]> {
// We assume there won't be more than 100 linked issues
const retrieveLinkedIssuesQuery = `
query ($repoOwner: String!, $repoName: String!, $prNumber: Int!) {
repository(owner: $repoOwner, name: $repoName) {
pullRequest(number: $prNumber) {
closingIssuesReferences(first: 100) {
nodes {
id
number
createdAt
repository {
name
owner {
login
}
}
}
}
}
}
}
`;
const retrieveLinkedIssuesResult: {
repository: {
pullRequest: {
closingIssuesReferences: {
nodes: Array<{
id: string;
number: number;
createdAt: string;
repository: {
name: string;
owner: {
login: string;
};
};
}>;
};
};
};
} = await octokit.graphql(retrieveLinkedIssuesQuery, {
repoOwner,
repoName,
prNumber
});
const linkedIssues = retrieveLinkedIssuesResult?.repository?.pullRequest?.closingIssuesReferences?.nodes?.map((issue: {
id: string;
number: number;
createdAt: string;
repository: {
name: string;
owner: {
login: string;
};
};
}) => {
return {
id: issue?.id,
number: issue?.number,
repoOwner: issue?.repository?.owner?.login,
repoName: issue?.repository?.name,
createdAt: issue?.createdAt
};
}) || [];
return linkedIssues;
}
// This function adds label to a labelable object (i.e. a pull request or an issue)
async function addLabelToLabelable(octokit: InstanceType<typeof GitHub>, labelable: Labelable, labelName: string, labelColor: string, labelDescription: string): Promise<void> {
// Retrieve label from the labelable's repo, or create label if required
const labelId = await createOrRetrieveLabel(octokit, labelable?.repoOwner, labelable?.repoName, labelName, labelColor, labelDescription);
const addLabelsToLabelableMutation = `
mutation AddLabelsToLabelable($labelableId: ID!, $labelIds: [ID!]!) {
addLabelsToLabelable(input: {labelableId: $labelableId, labelIds: $labelIds}) {
clientMutationId
}
}
`;
await octokit.graphql(addLabelsToLabelableMutation, {
labelableId: labelable?.id,
labelIds: [labelId],
});
}

View File

@ -1,177 +0,0 @@
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;
}

40
.github/workflows/add-release-label.yml vendored Normal file
View File

@ -0,0 +1,40 @@
name: Add release label to PR and linked issues when PR gets merged
on:
pull_request:
branches:
- develop
types:
- closed
jobs:
add-release-label:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0 # This is needed to checkout all branches
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: yarn
- name: Install dependencies
run: yarn --immutable
- name: Get the next semver version
id: get-next-semver-version
env:
FORCE_NEXT_SEMVER_VERSION: ${{ vars.FORCE_NEXT_SEMVER_VERSION }}
run: ./development/get-next-semver-version.sh "$FORCE_NEXT_SEMVER_VERSION"
- name: Add release label to PR and linked issues
id: add-release-label-to-pr-and-linked-issues
env:
RELEASE_LABEL_TOKEN: ${{ secrets.RELEASE_LABEL_TOKEN }}
NEXT_SEMVER_VERSION: ${{ env.NEXT_SEMVER_VERSION }}
run: npm run add-release-label-to-pr-and-linked-issues

View File

@ -17,10 +17,11 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
node-version-file: '.nvmrc'
cache: yarn
- name: Install dependencies
run: yarn
run: yarn --immutable
- name: Run fitness functions
env:
@ -31,4 +32,4 @@ jobs:
# 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"
npm run fitness-functions -- "ci" "./diff"

View File

@ -1,27 +0,0 @@
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

45
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Main
on:
push:
branches: [develop, master]
pull_request:
jobs:
check-workflows:
name: Check workflows
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Download actionlint
id: download-actionlint
run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/7fdc9630cc360ea1a469eed64ac6d78caeda1234/scripts/download-actionlint.bash) 1.6.23
shell: bash
- name: Check workflow files
run: ${{ steps.download-actionlint.outputs.executable }} -color
shell: bash
all-jobs-completed:
name: All jobs completed
runs-on: ubuntu-latest
needs:
- check-workflows
outputs:
PASSED: ${{ steps.set-output.outputs.PASSED }}
steps:
- name: Set PASSED output
id: set-output
run: echo "PASSED=true" >> "$GITHUB_OUTPUT"
all-jobs-pass:
name: All jobs pass
if: ${{ always() }}
runs-on: ubuntu-latest
needs: all-jobs-completed
steps:
- name: Check that all jobs have passed
run: |
passed="${{ needs.all-jobs-completed.outputs.PASSED }}"
if [[ $passed != "true" ]]; then
exit 1
fi

View File

@ -0,0 +1,43 @@
name: Remove labels after issue (or PR) closed
on:
issues:
types: [closed]
pull_request:
types: [closed]
jobs:
cleanup:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- name: Remove labels
env:
REPO: ${{ github.repository }}
ISSUE_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
LABELS=(
"product-backlog"
"needs-design"
"design-in-progress"
"ready-for-dev"
"sprint-backlog"
"in-progress"
"blocked"
"needs-dev-review"
"needs-qa"
"issues-found"
"ready-for-release"
)
for LABEL in "${LABELS[@]}"; do
curl \
-X DELETE \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/$REPO/issues/$ISSUE_NUMBER/labels/$LABEL"
done

22
.github/workflows/sonar.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Sonar
on:
workflow_call:
secrets:
SONAR_TOKEN:
required: true
jobs:
sonarcloud:
name: SonarCloud
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Shallow clones should be disabled for better relevancy of analysis
- name: SonarCloud Scan
# v1.9.1
uses: SonarSource/sonarcloud-github-action@5875562561d22a34be0c657405578705a169af6c
with:
args: >
-Dsonar.javascript.lcov.reportPaths=tests/coverage/lcov.info
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

View File

@ -0,0 +1,216 @@
name: Update LavaMoat policies
on:
issue_comment:
types: created
jobs:
is-fork-pull-request:
name: Determine whether this issue comment was on a pull request from a fork
if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '@metamaskbot update-policies') }}
runs-on: ubuntu-latest
outputs:
IS_FORK: ${{ steps.is-fork.outputs.IS_FORK }}
steps:
- uses: actions/checkout@v3
- name: Determine whether this PR is from a fork
id: is-fork
run: echo "IS_FORK=$(gh pr view --json isCrossRepository --jq '.isCrossRepository' "${PR_NUMBER}" )" >> "$GITHUB_OUTPUT"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.issue.number }}
react-to-comment:
name: React to the comment
runs-on: ubuntu-latest
needs: is-fork-pull-request
# Early exit if this is a fork, since later steps are skipped for forks
if: ${{ needs.is-fork-pull-request.outputs.IS_FORK == 'false' }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: React to the comment
run: |
gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/repos/${REPO}/issues/comments/${COMMENT_ID}/reactions" \
-f content='+1'
env:
COMMENT_ID: ${{ github.event.comment.id }}
GITHUB_TOKEN: ${{ secrets.LAVAMOAT_UPDATE_TOKEN }}
REPO: ${{ github.repository }}
prepare:
name: Prepare dependencies
runs-on: ubuntu-latest
needs: is-fork-pull-request
# Early exit if this is a fork, since later steps are skipped for forks
if: ${{ needs.is-fork-pull-request.outputs.IS_FORK == 'false' }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Checkout pull request
run: gh pr checkout "${PR_NUMBER}"
env:
GITHUB_TOKEN: ${{ secrets.LAVAMOAT_UPDATE_TOKEN }}
PR_NUMBER: ${{ github.event.issue.number }}
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install Yarn dependencies
run: yarn --immutable
update-lavamoat-build-policy:
name: Update LavaMoat build policy
runs-on: ubuntu-latest
needs:
- prepare
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Checkout pull request
run: gh pr checkout "${PR_NUMBER}"
env:
GITHUB_TOKEN: ${{ secrets.LAVAMOAT_UPDATE_TOKEN }}
PR_NUMBER: ${{ github.event.issue.number }}
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies from cache
run: yarn --immutable --immutable-cache
- name: Update LavaMoat build policy
run: yarn lavamoat:build:auto
- name: Cache build policy
uses: actions/cache/save@v3
with:
path: lavamoat/build-system
key: cache-build-${{ github.run_id }}-${{ github.run_attempt }}
update-lavamoat-webapp-policy:
strategy:
matrix:
# Ensure this is synchronized with the list below in the "commit-updated-policies" job
# and with the build type list in `builds.yml`
build-type: [main, beta, flask, mmi, desktop]
name: Update LavaMoat ${{ matrix.build-type }} application policy
runs-on: ubuntu-latest
needs:
- prepare
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Checkout pull request
run: gh pr checkout "${PR_NUMBER}"
env:
GITHUB_TOKEN: ${{ secrets.LAVAMOAT_UPDATE_TOKEN }}
PR_NUMBER: ${{ github.event.issue.number }}
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies from cache
run: yarn --immutable --immutable-cache
- name: Update LavaMoat ${{ matrix.build-type }} policy
run: yarn lavamoat:webapp:auto:ci '--build-types=${{ matrix.build-type }}'
env:
INFURA_PROJECT_ID: 00000000000
- name: Cache ${{ matrix.build-type }} application policy
uses: actions/cache/save@v3
with:
path: lavamoat/browserify/${{ matrix.build-type }}
key: cache-${{ matrix.build-type }}-${{ github.run_id }}-${{ github.run_attempt }}
commit-updated-policies:
name: Commit the updated LavaMoat policies
runs-on: ubuntu-latest
needs:
- is-fork-pull-request
- update-lavamoat-build-policy
- update-lavamoat-webapp-policy
# Ensure forks don't get access to the LavaMoat update token
if: ${{ needs.is-fork-pull-request.outputs.IS_FORK == 'false' }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
# Use PAT to ensure that the commit later can trigger status check workflows
token: ${{ secrets.LAVAMOAT_UPDATE_TOKEN }}
- name: Checkout pull request
run: gh pr checkout "${PR_NUMBER}"
env:
GITHUB_TOKEN: ${{ secrets.LAVAMOAT_UPDATE_TOKEN }}
PR_NUMBER: ${{ github.event.issue.number }}
- name: Restore build policy
uses: actions/cache/restore@v3
with:
path: lavamoat/build-system
key: cache-build-${{ github.run_id }}-${{ github.run_attempt }}
fail-on-cache-miss: true
# One restore step per build type: [main, beta, flask, mmi, desktop]
# Ensure this is synchronized with the list above in the "update-lavamoat-webapp-policy" job
# and with the build type list in `builds.yml`
- name: Restore main application policy
uses: actions/cache/restore@v3
with:
path: lavamoat/browserify/main
key: cache-main-${{ github.run_id }}-${{ github.run_attempt }}
fail-on-cache-miss: true
- name: Restore beta application policy
uses: actions/cache/restore@v3
with:
path: lavamoat/browserify/beta
key: cache-beta-${{ github.run_id }}-${{ github.run_attempt }}
fail-on-cache-miss: true
- name: Restore flask application policy
uses: actions/cache/restore@v3
with:
path: lavamoat/browserify/flask
key: cache-flask-${{ github.run_id }}-${{ github.run_attempt }}
fail-on-cache-miss: true
- name: Restore mmi application policy
uses: actions/cache/restore@v3
with:
path: lavamoat/browserify/mmi
key: cache-mmi-${{ github.run_id }}-${{ github.run_attempt }}
fail-on-cache-miss: true
- name: Restore desktop application policy
uses: actions/cache/restore@v3
with:
path: lavamoat/browserify/desktop
key: cache-desktop-${{ github.run_id }}-${{ github.run_attempt }}
fail-on-cache-miss: true
- name: Check whether there are policy changes
id: policy-changes
run: |
if git diff --exit-code
then
echo "HAS_CHANGES=false" >> "$GITHUB_OUTPUT"
else
echo "HAS_CHANGES=true" >> "$GITHUB_OUTPUT"
fi
- name: Commit the updated policies
if: steps.policy-changes.outputs.HAS_CHANGES == 'true'
run: |
git config --global user.name 'MetaMask Bot'
git config --global user.email 'metamaskbot@users.noreply.github.com'
git commit -am "Update LavaMoat policies"
git push
- name: Post comment
run: |
if [[ $HAS_CHANGES == 'true' ]]
then
gh pr comment "${PR_NUMBER}" --body 'Policies updated'
else
gh pr comment "${PR_NUMBER}" --body 'No policy changes'
fi
env:
HAS_CHANGES: ${{ steps.policy-changes.outputs.HAS_CHANGES }}
GITHUB_TOKEN: ${{ secrets.LAVAMOAT_UPDATE_TOKEN }}
PR_NUMBER: ${{ github.event.issue.number }}

9
.iyarc
View File

@ -3,6 +3,9 @@ GHSA-257v-vj4p-3w2h
# request library is subject to SSRF.
# addressed by temporary patch in .yarn/patches/request-npm-2.88.2-f4a57c72c4.patch
# NOTE: Disabled for now since this library seems to have been removed from the
# dependency tree — we can re-enable this line later if it appears again
#GHSA-p8p7-x288-28g6
GHSA-p8p7-x288-28g6
# Prototype pollution
# Not easily patched
# Minimal risk to us because we're using lockdown which also prevents this case of prototype pollution
GHSA-h755-8qp9-cq85

View File

@ -6,7 +6,6 @@ module.exports = {
'./app/scripts/migrations/*.test.js',
'./app/scripts/platforms/*.test.js',
'./app/scripts/controllers/app-state.test.js',
'./app/scripts/controllers/network/**/*.test.js',
'./app/scripts/controllers/permissions/**/*.test.js',
'./app/scripts/controllers/mmi-controller.test.js',
'./app/scripts/constants/error-utils.test.js',

View File

@ -1,6 +1,6 @@
import React, { Component, createContext, useMemo } from 'react';
import PropTypes from 'prop-types';
import { getMessage } from '../ui/helpers/utils/i18n-helper';
import { getMessage } from '../shared/modules/i18n';
import { I18nContext } from '../ui/contexts/i18n';
export { I18nContext };

View File

@ -6,6 +6,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [10.34.0]
### Added
- Add a security quiz to the SRP reveal ([#19283](https://github.com/MetaMask/metamask-extension/pull/19283))
- [FLASK] Add Snaps keyring and new snap accounts related pages ([#19710](https://github.com/MetaMask/metamask-extension/pull/19710))
### Changed
- Decrease boldness of text in some labels ([#19731](https://github.com/MetaMask/metamask-extension/pull/19731))
### Fixed
- Fix design inconsistencies in the connect flow ([#19800](https://github.com/MetaMask/metamask-extension/pull/19800))
- Fix connection issues on some dapps, and ensure that `eth_requestAccount` returns accounts when opening multiple tabs for the same dapp ([#19727](https://github.com/MetaMask/metamask-extension/pull/19727))
- Fix UI bugs in contacts page ([#19646](https://github.com/MetaMask/metamask-extension/pull/19646))
- Ensure correct logo shown on Linea ([#19717](https://github.com/MetaMask/metamask-extension/pull/19717))
- Fix the autolock field in settings on firefox ([#19653](https://github.com/MetaMask/metamask-extension/pull/19653))
- Prevent duplicate account names that only differ by letter casing ([#19616](https://github.com/MetaMask/metamask-extension/pull/19616))
- Ensure token details stay within asset dropdown border ([#19626](https://github.com/MetaMask/metamask-extension/pull/19626))
- Prevent rounded corners in account menu ([#19615](https://github.com/MetaMask/metamask-extension/pull/19615))
- Ensure network changes before the user accepts a wallet_watchAsset request add the NFT to pre-change chain ID and address ([#19629](https://github.com/MetaMask/metamask-extension/pull/19629))
- Fix performance degradations noticable on Firefox builds ([#19993](https://github.com/MetaMask/metamask-extension/pull/19993))
- Fix copy to clipboard of public address, so that it is only cleared from the clipboard after 60 seconds ([#19948](https://github.com/MetaMask/metamask-extension/pull/19948))
- Fix overlapping text, in some language, in home screen buttons ([#19920](https://github.com/MetaMask/metamask-extension/pull/19920))
## [10.33.1]
### Fixed
- Fix to bug causing users to see an infinite spinner when signing typed messages. ([#19894](https://github.com/MetaMask/metamask-extension/pull/19894))
@ -208,7 +232,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Additional incoming transactions support ([#14219](https://github.com/MetaMask/metamask-extension/pull/14219))
### Changed
- UX: Loaclize the avatar-favicon description text ([#18132](https://github.com/MetaMask/metamask-extension/pull/18132))
- UX: Localize the avatar-favicon description text ([#18132](https://github.com/MetaMask/metamask-extension/pull/18132))
- 17921 Update TransactionAlerts with BannerAlert ([#17940](https://github.com/MetaMask/metamask-extension/pull/17940))
- Part of 17670: Replace Typography with Text confirm-approve-content.component.js and home.component.js ([#18049](https://github.com/MetaMask/metamask-extension/pull/18049))
- UX: Icon: Update buy icon ([#18123](https://github.com/MetaMask/metamask-extension/pull/18123))
@ -3829,7 +3853,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Uncategorized
- Added the ability to restore accounts from seed words.
[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v10.33.1...HEAD
[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v10.34.0...HEAD
[10.34.0]: https://github.com/MetaMask/metamask-extension/compare/v10.33.1...v10.34.0
[10.33.1]: https://github.com/MetaMask/metamask-extension/compare/v10.33.0...v10.33.1
[10.33.0]: https://github.com/MetaMask/metamask-extension/compare/v10.32.0...v10.33.0
[10.32.0]: https://github.com/MetaMask/metamask-extension/compare/v10.31.1...v10.32.0

View File

@ -112,19 +112,22 @@ Whenever you change dependencies (adding, removing, or updating, either in `pack
- The `allow-scripts` configuration in `package.json`
- Run `yarn allow-scripts auto` to update the `allow-scripts` configuration automatically. This config determines whether the package's install/postinstall scripts are allowed to run. Review each new package to determine whether the install script needs to run or not, testing if necessary.
- Unfortunately, `yarn allow-scripts auto` will behave inconsistently on different platforms. macOS and Windows users may see extraneous changes relating to optional dependencies.
- The LavaMoat policy files. The _tl;dr_ is to run `yarn lavamoat:auto` to update these files, but there can be devils in the details:
- There are two sets of LavaMoat policy files:
- The production LavaMoat policy files (`lavamoat/browserify/*/policy.json`), which are re-generated using `yarn lavamoat:background:auto`. Add `--help` for usage.
- These should be regenerated whenever the production dependencies for the background change.
- The build system LavaMoat policy file (`lavamoat/build-system/policy.json`), which is re-generated using `yarn lavamoat:build:auto`.
- This should be regenerated whenever the dependencies used by the build system itself change.
- Whenever you regenerate a policy file, review the changes to determine whether the access granted to each package seems appropriate.
- Unfortunately, `yarn lavamoat:auto` will behave inconsistently on different platforms.
macOS and Windows users may see extraneous changes relating to optional dependencies.
- If you keep getting policy failures even after regenerating the policy files, try regenerating the policies after a clean install by doing:
- `rm -rf node_modules/ && yarn && yarn lavamoat:auto`
- Keep in mind that any kind of dynamic import or dynamic use of globals may elude LavaMoat's static analysis.
Refer to the LavaMoat documentation or ask for help if you run into any issues.
- The LavaMoat policy files
- If you are a MetaMask team member and your PR is on a repository branch, you can use the bot command `@metamaskbot update-policies` to ask the MetaMask bot to automatically update the policies for you.
- If your PR is from a fork, you can ask a MetaMask team member to help with updating the policy files.
- Manual update instructions: The _tl;dr_ is to run `yarn lavamoat:auto` to update these files, but there can be devils in the details:
- There are two sets of LavaMoat policy files:
- The production LavaMoat policy files (`lavamoat/browserify/*/policy.json`), which are re-generated using `yarn lavamoat:background:auto`. Add `--help` for usage.
- These should be regenerated whenever the production dependencies for the background change.
- The build system LavaMoat policy file (`lavamoat/build-system/policy.json`), which is re-generated using `yarn lavamoat:build:auto`.
- This should be regenerated whenever the dependencies used by the build system itself change.
- Whenever you regenerate a policy file, review the changes to determine whether the access granted to each package seems appropriate.
- Unfortunately, `yarn lavamoat:auto` will behave inconsistently on different platforms.
macOS and Windows users may see extraneous changes relating to optional dependencies.
- If you keep getting policy failures even after regenerating the policy files, try regenerating the policies after a clean install by doing:
- `rm -rf node_modules/ && yarn && yarn lavamoat:auto`
- Keep in mind that any kind of dynamic import or dynamic use of globals may elude LavaMoat's static analysis.
Refer to the LavaMoat documentation or ask for help if you run into any issues.
## Architecture

View File

@ -202,9 +202,6 @@
"delete": {
"message": "ሰርዝ"
},
"deleteAccount": {
"message": "መለያን ሰርዝ"
},
"deleteNetwork": {
"message": "አውታረ መረብ ይሰረዝ?"
},

View File

@ -215,9 +215,6 @@
"delete": {
"message": "حذف"
},
"deleteAccount": {
"message": "حذف الحساب"
},
"deleteNetwork": {
"message": "هل تريد حذف الشبكة؟"
},

View File

@ -211,9 +211,6 @@
"delete": {
"message": "Изтриване"
},
"deleteAccount": {
"message": "Изтриване на акаунт"
},
"deleteNetwork": {
"message": "Да се изтрие ли мрежата?"
},

View File

@ -208,9 +208,6 @@
"delete": {
"message": "মুছুন"
},
"deleteAccount": {
"message": "অ্যাকাউন্ট মুছুন"
},
"deleteNetwork": {
"message": "নেটওয়ার্ক মুছবেন?"
},

View File

@ -208,9 +208,6 @@
"delete": {
"message": "Suprimeix"
},
"deleteAccount": {
"message": "Elimina el compte"
},
"deleteNetwork": {
"message": "Esborrar Xarxa?"
},

View File

@ -211,9 +211,6 @@
"delete": {
"message": "Slet"
},
"deleteAccount": {
"message": "Slet konto"
},
"deleteNetwork": {
"message": "Slet Netværk?"
},

View File

@ -911,9 +911,6 @@
"delete": {
"message": "Löschen"
},
"deleteAccount": {
"message": "Konto löschen"
},
"deleteNetwork": {
"message": "Netzwerk löschen?"
},
@ -1562,6 +1559,12 @@
"message": "Betrüger aber schon.",
"description": "The text link in 'holdToRevealContent3'"
},
"holdToRevealSRP": {
"message": "Halten, um GWP anzuzeigen"
},
"holdToRevealSRPTitle": {
"message": "Bewahren Sie Ihre GWP sicher auf"
},
"ignoreAll": {
"message": "Alle ignorieren"
},
@ -1596,9 +1599,6 @@
"importNFTTokenIdToolTip": {
"message": "Die ID eines NFTs ist eine eindeutige Kennung, da keine zwei NFTs gleich sind. Auch diese Nummer finden Sie in OpenSea unter „Details“. Notieren Sie diese oder kopieren Sie sie in Ihre Zwischenablage."
},
"importNFTs": {
"message": "NFTs importieren"
},
"importSelectedTokens": {
"message": "Ausgewählte Token importieren?"
},
@ -1857,9 +1857,6 @@
"lock": {
"message": "Ausloggen"
},
"lockTimeTooGreat": {
"message": "Sperrzeit ist zu groß"
},
"logo": {
"message": "$1-Logo",
"description": "$1 is the name of the ticker"
@ -1953,9 +1950,6 @@
"mismatchedRpcUrl": {
"message": "Laut unseren Aufzeichnungen stimmt der angegebene RPC-URL-Wert nicht mit einem bekannten Provider für diese Chain-ID überein."
},
"missingNFT": {
"message": "Sie sehen Ihr NFT nicht?"
},
"missingSetting": {
"message": "Sie können eine Einstellung nicht finden?"
},
@ -2162,9 +2156,6 @@
"noConversionRateAvailable": {
"message": "Kein Umrechnungskurs verfügbar"
},
"noNFTs": {
"message": "Noch keine NFTs"
},
"noSnaps": {
"message": "Keine Snaps installiert"
},
@ -3333,12 +3324,63 @@
},
"srpPasteFailedTooManyWords": {
"message": "Das Einfügen schlug fehl, weil sie mehr als 24 Wörter enthielt. Eine geheime Wiederherstellungsphrase darf maximal 24 Wörter enthalten.",
"description": "Description of SRP paste erorr when the pasted content has too many words"
"description": "Description of SRP paste error when the pasted content has too many words"
},
"srpPasteTip": {
"message": "Sie können Ihre gesamte geheime Wiederherstellungsphrase in ein beliebiges Feld einfügen",
"description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly."
},
"srpSecurityQuizGetStarted": {
"message": "Anfangen"
},
"srpSecurityQuizIntroduction": {
"message": "Zur Enthüllung Ihrer geheimen Wiederherstellungsphrase, müssen Sie zwei Fragen beantworten"
},
"srpSecurityQuizQuestionOneQuestion": {
"message": "Wenn Sie Ihre geheime Wiederherstellungsphrase verlieren, kann MetaMask ..."
},
"srpSecurityQuizQuestionOneRightAnswer": {
"message": "Ihnen nicht helfen"
},
"srpSecurityQuizQuestionOneRightAnswerDescription": {
"message": "Schreiben Sie sie auf, gravieren Sie sie in Metall ein oder bewahren Sie sie an mehreren geheimen Orten auf, damit Sie sie niemals verlieren. Sollten Sie sie verlieren, ist sie für immer weg."
},
"srpSecurityQuizQuestionOneRightAnswerTitle": {
"message": "Richtig! Niemand kann Ihnen dabei helfen, Ihre geheime Wiederherstellungsphrase zurückzubekommen"
},
"srpSecurityQuizQuestionOneWrongAnswer": {
"message": "diese für Sie zurückzubekommen"
},
"srpSecurityQuizQuestionOneWrongAnswerDescription": {
"message": "Wenn Sie Ihre geheime Wiederherstellungsphrase verlieren, ist diese für immer verloren. Niemand kann Ihnen dabei helfen, sie zurückzubekommen, egal, was behauptet wird."
},
"srpSecurityQuizQuestionOneWrongAnswerTitle": {
"message": "Falsch! Niemand kann Ihnen dabei helfen, Ihre geheime Wiederherstellungsphrase zurückzubekommen"
},
"srpSecurityQuizQuestionTwoQuestion": {
"message": "Sollte jemand, selbst ein Support-Mitarbeiter, nach Ihrer geheimen Wiederherstellungsphrase fragen ..."
},
"srpSecurityQuizQuestionTwoRightAnswer": {
"message": "Werden Sie betrogen"
},
"srpSecurityQuizQuestionTwoRightAnswerDescription": {
"message": "Jeder, der behauptet, Ihre gemeine Wiederherstellungsphrase zu benötigen, lügt Sie an. Wenn Sie diese mit solchen Personen teilen, werden diese Ihre Vermögenswerte stehlen."
},
"srpSecurityQuizQuestionTwoRightAnswerTitle": {
"message": "Richtig! Es ist nie eine gute Idee, Ihre geheime Wiederherstellungsphrase mit anderen zu teilen"
},
"srpSecurityQuizQuestionTwoWrongAnswer": {
"message": "Sie sollten sie dieser Person geben"
},
"srpSecurityQuizQuestionTwoWrongAnswerDescription": {
"message": "Jeder, der behauptet, Ihre gemeine Wiederherstellungsphrase zu benötigen, lügt Sie an. Wenn Sie diese mit einer solchen Person teilen, wird sie Ihre Vermögenswerte stehlen."
},
"srpSecurityQuizQuestionTwoWrongAnswerTitle": {
"message": "Nein! Teilen Sie Ihre geheime Wiederherstellungsphrase mit niemandem, niemals"
},
"srpSecurityQuizTitle": {
"message": "Sicherheits-Quiz"
},
"srpToggleShow": {
"message": "Dieses Wort der geheimen Wiederherstellungsphrase anzeigen/ausblenden",
"description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase"

View File

@ -911,9 +911,6 @@
"delete": {
"message": "Διαγραφή"
},
"deleteAccount": {
"message": "Διαγραφή Λογαριασμού"
},
"deleteNetwork": {
"message": "Διαγραφή Δικτύου;"
},
@ -1562,6 +1559,12 @@
"message": "αλλά οι απατεώνες μπορεί να το κάνουν.",
"description": "The text link in 'holdToRevealContent3'"
},
"holdToRevealSRP": {
"message": "Κρατήστε το πατημένο για να αποκαλυφθεί το ΜΦΑ"
},
"holdToRevealSRPTitle": {
"message": "Κρατήστε το ΜΦΑ σας ασφαλές"
},
"ignoreAll": {
"message": "Αγνόηση όλων"
},
@ -1596,9 +1599,6 @@
"importNFTTokenIdToolTip": {
"message": "Ένα αναγνωριστικό του συλλεκτικού είναι ένα μοναδικό αναγνωριστικό δεδομένου ότι δεν υπάρχουν δύο ίδια NFT. Και πάλι, στο OpenSea αυτός ο αριθμός βρίσκεται στην ενότητα \"Λεπτομέρειες\". Σημειώστε τον ή αντιγράψτε τον στο πρόχειρο σας."
},
"importNFTs": {
"message": "Εισαγωγή NFT"
},
"importSelectedTokens": {
"message": "Εισαγωγή επιλεγμένων token;"
},
@ -1857,9 +1857,6 @@
"lock": {
"message": "Αποσύνδεση"
},
"lockTimeTooGreat": {
"message": "Ο χρόνος κλειδώματος είναι πολύ μεγάλος"
},
"logo": {
"message": "Λογότυπο $1",
"description": "$1 is the name of the ticker"
@ -1953,9 +1950,6 @@
"mismatchedRpcUrl": {
"message": "Σύμφωνα με τις καταχωρήσεις μας, η τιμή RPC URL που υποβλήθηκε δεν ταιριάζει με κάποιον γνωστό πάροχο για αυτό το αναγνωριστικό αλυσίδας."
},
"missingNFT": {
"message": "Δεν βλέπετε το NFT σας;"
},
"missingSetting": {
"message": "Δεν μπορείτε να βρείτε μια ρύθμιση;"
},
@ -2162,9 +2156,6 @@
"noConversionRateAvailable": {
"message": "Δεν Υπάρχει Διαθέσιμη Ισοτιμία Μετατροπής"
},
"noNFTs": {
"message": "Δεν υπάρχουν NFT ακόμα"
},
"noSnaps": {
"message": "Δεν εγκαταστάθηκαν Snaps"
},
@ -3330,12 +3321,63 @@
},
"srpPasteFailedTooManyWords": {
"message": "Η επικόλληση απέτυχε επειδή περιείχε περισσότερες από 24 λέξεις. Μια μυστική φράση ανάκτησης μπορεί να αποτελείται από το πολύ 24 λέξεις.",
"description": "Description of SRP paste erorr when the pasted content has too many words"
"description": "Description of SRP paste error when the pasted content has too many words"
},
"srpPasteTip": {
"message": "Μπορείτε να επικολλήσετε ολόκληρη τη μυστική φράση ανάκτησής σας σε οποιοδήποτε πεδίο",
"description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly."
},
"srpSecurityQuizGetStarted": {
"message": "Ξεκινήστε"
},
"srpSecurityQuizIntroduction": {
"message": "Για να σας αποκαλύψουμε τη Μυστική Φράση Ανάκτησης, πρέπει να απαντήσετε σωστά σε δύο ερωτήσεις"
},
"srpSecurityQuizQuestionOneQuestion": {
"message": "Αν χάσετε τη Μυστική Φράση Ανάκτησης, το MetaMask..."
},
"srpSecurityQuizQuestionOneRightAnswer": {
"message": "Δεν μπορεί να σας βοηθήσει"
},
"srpSecurityQuizQuestionOneRightAnswerDescription": {
"message": "Γράψτε την κάπου, χαράξτε την πάνω σε μέταλλο ή κρατήστε την σε πολλά μυστικά σημεία για να μην την χάσετε ποτέ. Αν την χάσετε, θα χαθεί για πάντα."
},
"srpSecurityQuizQuestionOneRightAnswerTitle": {
"message": "Σωστά! Κανείς δεν μπορεί να σας βοηθήσει να επαναφέρετε τη Μυστική Φράση Ανάκτησης"
},
"srpSecurityQuizQuestionOneWrongAnswer": {
"message": "Μπορεί να την επαναφέρει για εσάς"
},
"srpSecurityQuizQuestionOneWrongAnswerDescription": {
"message": "Αν χάσετε τη Μυστική Φράση Ανάκτησης, θα την χάσετε για πάντα. Κανείς δεν μπορεί να σας βοηθήσει να την επαναφέρετε, ό,τι κι αν σας πει."
},
"srpSecurityQuizQuestionOneWrongAnswerTitle": {
"message": "Λάθος! Κανείς δεν μπορεί να σας βοηθήσει να επαναφέρετε τη Μυστική Φράση Ανάκτησης"
},
"srpSecurityQuizQuestionTwoQuestion": {
"message": "Αν κάποιος, ακόμα και ένας τεχνικός υποστήριξης, σας ζητήσει τη Μυστική Φράση Ανάκτησης..."
},
"srpSecurityQuizQuestionTwoRightAnswer": {
"message": "Σας έχουν εξαπατήσει"
},
"srpSecurityQuizQuestionTwoRightAnswerDescription": {
"message": "Όποιος ισχυριστεί ότι χρειάζεται τη Μυστική Φράση Ανάκτησης, σας λέει ψέματα. Αν την μοιραστείτε μαζί του, θα κλέψει τα περιουσιακά σας στοιχεία."
},
"srpSecurityQuizQuestionTwoRightAnswerTitle": {
"message": "Σωστά! Το να μοιράζεστε τη Μυστική Φράση Ανάκτησης δεν είναι καλή ιδέα"
},
"srpSecurityQuizQuestionTwoWrongAnswer": {
"message": "Πρέπει να τους την δώσετε"
},
"srpSecurityQuizQuestionTwoWrongAnswerDescription": {
"message": "Όποιος ισχυριστεί ότι χρειάζεται τη Μυστική Φράση Ανάκτησης, σας λέει ψέματα. Αν την μοιραστείτε μαζί του, θα κλέψει τα περιουσιακά σας στοιχεία."
},
"srpSecurityQuizQuestionTwoWrongAnswerTitle": {
"message": "Όχι! Ποτέ μα ποτέ μην μοιραστείτε με κανέναν τη Μυστική Φράση Ανάκτησης"
},
"srpSecurityQuizTitle": {
"message": "Κουίζ Ασφαλείας"
},
"srpToggleShow": {
"message": "Εμφάνιση/Απόκρυψη αυτής της λέξης από τη μυστική φράση ανάκτησης",
"description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase"

View File

@ -271,6 +271,9 @@
"addNfts": {
"message": "Add NFTs"
},
"addSnapAccountModalDescription": {
"message": "Discover options to keep your account secure with MetaMask Snaps"
},
"addSuggestedNFTs": {
"message": "Add suggested NFTs"
},
@ -670,9 +673,6 @@
"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."
},
@ -706,6 +706,21 @@
"complianceSettingsExplanation": {
"message": "Change your settings or view reports by opening up Codefi Compliance or disconnect below."
},
"configureSnapPopupDescription": {
"message": "You're now leaving MetaMask to configure this snap."
},
"configureSnapPopupInstallDescription": {
"message": "You're now leaving MetaMask to install this snap."
},
"configureSnapPopupInstallTitle": {
"message": "Install snap"
},
"configureSnapPopupLink": {
"message": "Click this link to continue:"
},
"configureSnapPopupTitle": {
"message": "Configure snap"
},
"confirm": {
"message": "Confirm"
},
@ -1101,8 +1116,8 @@
"delete": {
"message": "Delete"
},
"deleteAccount": {
"message": "Delete account"
"deleteContact": {
"message": "Delete contact"
},
"deleteNetwork": {
"message": "Delete network?"
@ -1701,6 +1716,9 @@
"general": {
"message": "General"
},
"getStarted": {
"message": "Get started"
},
"globalTitle": {
"message": "Global menu"
},
@ -1884,9 +1902,6 @@
"importNFTTokenIdToolTip": {
"message": "An NFT's ID is a unique identifier since no two NFTs are alike. Again, on OpenSea this number is under 'Details'. Make a note of it, or copy it onto your clipboard."
},
"importNFTs": {
"message": "Import NFTs"
},
"importSelectedTokens": {
"message": "Import selected tokens?"
},
@ -2171,8 +2186,8 @@
"lockMetaMask": {
"message": "Lock MetaMask"
},
"lockTimeTooGreat": {
"message": "Lock time is too great"
"lockTimeInvalid": {
"message": "Lock time must be a number between 0 and 10080"
},
"logo": {
"message": "$1 logo",
@ -2273,9 +2288,6 @@
"mismatchedRpcUrl": {
"message": "According to our records the submitted RPC URL value does not match a known provider for this chain ID."
},
"missingNFT": {
"message": "Don't see your NFT?"
},
"missingSetting": {
"message": "Can't find a setting?"
},
@ -2559,6 +2571,30 @@
"notePlaceholder": {
"message": "The approver will see this note when approving the transaction at the custodian."
},
"notificationTransactionFailedMessage": {
"message": "Transaction $1 failed! $2",
"description": "Content of the browser notification that appears when a transaction fails"
},
"notificationTransactionFailedMessageMMI": {
"message": "Transaction failed! $1",
"description": "Content of the browser notification that appears when a transaction fails in MMI"
},
"notificationTransactionFailedTitle": {
"message": "Failed transaction",
"description": "Title of the browser notification that appears when a transaction fails"
},
"notificationTransactionSuccessMessage": {
"message": "Transaction $1 confirmed!",
"description": "Content of the browser notification that appears when a transaction is confirmed"
},
"notificationTransactionSuccessTitle": {
"message": "Confirmed transaction",
"description": "Title of the browser notification that appears when a transaction is confirmed"
},
"notificationTransactionSuccessView": {
"message": "View on $1",
"description": "Additional content in browser notification that appears when a transaction is confirmed and has a block explorer URL"
},
"notifications": {
"message": "Notifications"
},
@ -3108,6 +3144,10 @@
"message": "Allow the snap to run indefinitely while, for example, processing large amounts of data.",
"description": "An extended description for the `endowment:long-running` permission"
},
"permission_manageAccounts": {
"message": "Add and control Ethereum accounts",
"description": "The description for `snap_manageAccounts` permission"
},
"permission_manageBip32Keys": {
"message": "Control your accounts and assets under $1 ($2).",
"description": "The description for the `snap_getBip32Entropy` permission. $1 is a derivation path, e.g. 'm/44'/0'/0''. $2 is the elliptic curve name, e.g. 'secp256k1'."
@ -3721,6 +3761,9 @@
"message": "Set a spending cap for your $1",
"description": "$1 is a token symbol"
},
"settingAddSnapAccount": {
"message": "Add snap account"
},
"settings": {
"message": "Settings"
},
@ -3825,6 +3868,9 @@
"smartSwapsSubDescription": {
"message": "* Smart Swaps will attempt to submit your transaction privately, multiple times. If all attempts fail, the transaction will be broadcast publicly to ensure your Swap successfully goes through."
},
"snapConfigure": {
"message": "Configure"
},
"snapConnectionWarning": {
"message": "$1 wants to connect to $2. Only continue if you trust this website.",
"description": "$2 is the snap and $1 is the dapp requesting connection to the snap."
@ -3833,6 +3879,47 @@
"message": "This content is coming from $1",
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
},
"snapCreateAccountSubtitle": {
"message": "Choose how to secure your new account using MetaMask Snaps."
},
"snapCreateAccountTitle": {
"message": "Create a $1 account",
"description": "Title of the Create Snap Account Page, $1 is the text using a different color"
},
"snapCreateAccountTitle2": {
"message": "snap",
"description": "$1 of the snapCreateAccountTitle"
},
"snapCreatedByMetaMask": {
"message": "By MetaMask"
},
"snapDetailAudits": {
"message": "Audit"
},
"snapDetailDeveloper": {
"message": "Developer"
},
"snapDetailLastUpdated": {
"message": "Updated"
},
"snapDetailManageSnap": {
"message": "Manage snap"
},
"snapDetailTags": {
"message": "Tags"
},
"snapDetailVersion": {
"message": "Version"
},
"snapDetailWebsite": {
"message": "Website"
},
"snapDetailsCreateASnapAccount": {
"message": "Create a Snap Account"
},
"snapDetailsInstalled": {
"message": "Installed"
},
"snapError": {
"message": "Snap Error: '$1'. Error Code: '$2'",
"description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code."
@ -3874,6 +3961,9 @@
"message": "Installation failed",
"description": "Error title used when snap installation fails."
},
"snapIsAudited": {
"message": "Audited"
},
"snapResultError": {
"message": "Error"
},
@ -3886,6 +3976,9 @@
"snapUpdate": {
"message": "Update snap"
},
"snapUpdateAvailable": {
"message": "Update available"
},
"snapUpdateErrorDescription": {
"message": "$1 couldnt be updated.",
"description": "Error description used when snap update fails. $1 is the snap name."
@ -4000,12 +4093,66 @@
},
"srpPasteFailedTooManyWords": {
"message": "Paste failed because it contained over 24 words. A secret recovery phrase can have a maximum of 24 words.",
"description": "Description of SRP paste erorr when the pasted content has too many words"
"description": "Description of SRP paste error when the pasted content has too many words"
},
"srpPasteTip": {
"message": "You can paste your entire secret recovery phrase into any field",
"description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly."
},
"srpSecurityQuizGetStarted": {
"message": "Get started"
},
"srpSecurityQuizImgAlt": {
"message": "An eye with a keyhole in the center, and three floating password fields"
},
"srpSecurityQuizIntroduction": {
"message": "To reveal your Secret Recovery Phrase, you need to correctly answer two questions"
},
"srpSecurityQuizQuestionOneQuestion": {
"message": "If you lose your Secret Recovery Phrase, MetaMask..."
},
"srpSecurityQuizQuestionOneRightAnswer": {
"message": "Cant help you"
},
"srpSecurityQuizQuestionOneRightAnswerDescription": {
"message": "Write it down, engrave it on metal, or keep it in multiple secret spots so you never lose it. If you lose it, its gone forever."
},
"srpSecurityQuizQuestionOneRightAnswerTitle": {
"message": "Right! No one can help get your Secret Recovery Phrase back"
},
"srpSecurityQuizQuestionOneWrongAnswer": {
"message": "Can get it back for you"
},
"srpSecurityQuizQuestionOneWrongAnswerDescription": {
"message": "If you lose your Secret Recovery Phrase, its gone forever. No one can help you get it back, no matter what they might say."
},
"srpSecurityQuizQuestionOneWrongAnswerTitle": {
"message": "Wrong! No one can help get your Secret Recovery Phrase back"
},
"srpSecurityQuizQuestionTwoQuestion": {
"message": "If anyone, even a support agent, asks for your Secret Recovery Phrase..."
},
"srpSecurityQuizQuestionTwoRightAnswer": {
"message": "Youre being scammed"
},
"srpSecurityQuizQuestionTwoRightAnswerDescription": {
"message": "Anyone claiming to need your Secret Recovery Phrase is lying to you. If you share it with them, they will steal your assets."
},
"srpSecurityQuizQuestionTwoRightAnswerTitle": {
"message": "Correct! Sharing your Secret Recovery Phrase is never a good idea"
},
"srpSecurityQuizQuestionTwoWrongAnswer": {
"message": "You should give it to them"
},
"srpSecurityQuizQuestionTwoWrongAnswerDescription": {
"message": "Anyone claiming to need your Secret Recovery Phrase is lying to you. If you share it with them, they will steal your assets."
},
"srpSecurityQuizQuestionTwoWrongAnswerTitle": {
"message": "Nope! Never share your Secret Recovery Phrase with anyone, ever"
},
"srpSecurityQuizTitle": {
"message": "Security quiz"
},
"srpToggleShow": {
"message": "Show/Hide this word of the secret recovery phrase",
"description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase"
@ -5084,6 +5231,9 @@
"viewOnOpensea": {
"message": "View on Opensea"
},
"viewPortfolioDashboard": {
"message": "View Portfolio Dashboard"
},
"viewinCustodianApp": {
"message": "View in custodian app"
},

View File

@ -911,9 +911,6 @@
"delete": {
"message": "Eliminar"
},
"deleteAccount": {
"message": "Eliminar cuenta"
},
"deleteNetwork": {
"message": "¿Eliminar red?"
},
@ -1562,6 +1559,12 @@
"message": "pero los defraudadores sí.",
"description": "The text link in 'holdToRevealContent3'"
},
"holdToRevealSRP": {
"message": "Mantén presionado para revelar su SRP"
},
"holdToRevealSRPTitle": {
"message": "Proteja su SRP"
},
"ignoreAll": {
"message": "Ignorar todo"
},
@ -1596,9 +1599,6 @@
"importNFTTokenIdToolTip": {
"message": "La ID de un NFT es un identificador único, ya que no hay dos NFT iguales. Nuevamente, en OpenSea, este número se encuentra en 'Detalles'. Tome nota de ello o cópielo en su portapapeles."
},
"importNFTs": {
"message": "AGREGAR NFT"
},
"importSelectedTokens": {
"message": "¿Agregar los tokens seleccionados?"
},
@ -1857,9 +1857,6 @@
"lock": {
"message": "Bloquear"
},
"lockTimeTooGreat": {
"message": "El tiempo de bloqueo es demasiado largo"
},
"logo": {
"message": "Logo de $1",
"description": "$1 is the name of the ticker"
@ -1953,9 +1950,6 @@
"mismatchedRpcUrl": {
"message": "Según nuestros registros, el valor de la URL de RPC enviado no coincide con un proveedor conocido para este ID de cadena."
},
"missingNFT": {
"message": "¿No ve su NFT?"
},
"missingSetting": {
"message": "¿No puede encontrar un ajuste?"
},
@ -2162,9 +2156,6 @@
"noConversionRateAvailable": {
"message": "No hay tasa de conversión disponible"
},
"noNFTs": {
"message": "No hay ningún NFT aún"
},
"noSnaps": {
"message": "No hay complementos instalados"
},
@ -3333,12 +3324,63 @@
},
"srpPasteFailedTooManyWords": {
"message": "Pegar falló porque contenía más de 24 palabras. Una frase de recuperación secreta puede tener un máximo de 24 palabras.",
"description": "Description of SRP paste erorr when the pasted content has too many words"
"description": "Description of SRP paste error when the pasted content has too many words"
},
"srpPasteTip": {
"message": "Puede pegar toda su frase secreta de recuperación en cualquier campo",
"description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly."
},
"srpSecurityQuizGetStarted": {
"message": "Iniciar"
},
"srpSecurityQuizIntroduction": {
"message": "Para revelar su frase secreta de recuperación, debe responder correctamente dos preguntas"
},
"srpSecurityQuizQuestionOneQuestion": {
"message": "Si extravía su frase secreta de recuperación, MetaMask..."
},
"srpSecurityQuizQuestionOneRightAnswer": {
"message": "No puede ayudarlo"
},
"srpSecurityQuizQuestionOneRightAnswerDescription": {
"message": "Anótela, grábela en metal o guárdela en múltiples lugares secretos para que nunca la pierda. Si la extravía, se ha ido para siempre."
},
"srpSecurityQuizQuestionOneRightAnswerTitle": {
"message": "¡Correcto! Nadie puede ayudarlo a recuperar su frase secreta de recuperación"
},
"srpSecurityQuizQuestionOneWrongAnswer": {
"message": "Puede recuperarla para usted"
},
"srpSecurityQuizQuestionOneWrongAnswerDescription": {
"message": "Si pierde su frase secreta de recuperación, ésta desaparecerá para siempre. Nadie puede ayudarle a recuperarla, sin importar lo que digan."
},
"srpSecurityQuizQuestionOneWrongAnswerTitle": {
"message": "¡Incorrecto! Nadie puede ayudarlo a recuperar su frase secreta de recuperación"
},
"srpSecurityQuizQuestionTwoQuestion": {
"message": "Si alguien, incluso un agente de soporte, le pide su frase secreta de recuperación..."
},
"srpSecurityQuizQuestionTwoRightAnswer": {
"message": "Lo están estafando"
},
"srpSecurityQuizQuestionTwoRightAnswerDescription": {
"message": "Cualquiera que afirme necesitar su frase secreta de recuperación le está mintiendo. Si la comparte, le robarán sus activos."
},
"srpSecurityQuizQuestionTwoRightAnswerTitle": {
"message": "¡Correcto! Compartir su frase secreta de recuperación nunca es una buena idea"
},
"srpSecurityQuizQuestionTwoWrongAnswer": {
"message": "Debiera brindársela"
},
"srpSecurityQuizQuestionTwoWrongAnswerDescription": {
"message": "Cualquiera que afirme necesitar su frase secreta de recuperación le está mintiendo. Si la comparte, le robarán sus activos."
},
"srpSecurityQuizQuestionTwoWrongAnswerTitle": {
"message": "¡No! Nunca comparta su frase secreta de recuperación con nadie, nunca"
},
"srpSecurityQuizTitle": {
"message": "Cuestionario de seguridad"
},
"srpToggleShow": {
"message": "Mostrar/Ocultar esta palabra de la frase secreta de recuperación",
"description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase"

View File

@ -581,9 +581,6 @@
"delete": {
"message": "Eliminar"
},
"deleteAccount": {
"message": "Eliminar cuenta"
},
"deleteNetwork": {
"message": "¿Eliminar red?"
},
@ -1073,9 +1070,6 @@
"importMyWallet": {
"message": "Importar Mi cartera"
},
"importNFTs": {
"message": "Importar NFT"
},
"importTokenQuestion": {
"message": "¿Desea importar el token?"
},
@ -1269,9 +1263,6 @@
"lock": {
"message": "Bloquear"
},
"lockTimeTooGreat": {
"message": "El tiempo de bloqueo es demasiado largo"
},
"low": {
"message": "Baja"
},
@ -1336,9 +1327,6 @@
"message": "verifique los detalles de la red",
"description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key."
},
"missingNFT": {
"message": "¿No ve su NFT?"
},
"mustSelectOne": {
"message": "Debe seleccionar al menos 1 token."
},
@ -1471,9 +1459,6 @@
"noConversionRateAvailable": {
"message": "No hay tasa de conversión disponible"
},
"noNFTs": {
"message": "Aún no hay NFT"
},
"noTransactions": {
"message": "No tiene transacciones"
},

View File

@ -211,9 +211,6 @@
"delete": {
"message": "Kustuta"
},
"deleteAccount": {
"message": "Kustuta konto"
},
"deleteNetwork": {
"message": "Võrk kustutada?"
},

View File

@ -211,9 +211,6 @@
"delete": {
"message": "حذف"
},
"deleteAccount": {
"message": "حذف حساب"
},
"deleteNetwork": {
"message": "شبکه حذف شود؟"
},

View File

@ -211,9 +211,6 @@
"delete": {
"message": "Poista"
},
"deleteAccount": {
"message": "Poista tili"
},
"deleteNetwork": {
"message": "Poistetaanko verkko?"
},

View File

@ -187,9 +187,6 @@
"delete": {
"message": "I-delete"
},
"deleteAccount": {
"message": "I-delete ang Account"
},
"deleteNetwork": {
"message": "I-delete ang Network?"
},

View File

@ -911,9 +911,6 @@
"delete": {
"message": "Supprimer"
},
"deleteAccount": {
"message": "Supprimer le compte"
},
"deleteNetwork": {
"message": "Supprimer le réseau ?"
},
@ -1562,6 +1559,12 @@
"message": "mais les hameçonneurs pourraient le faire.",
"description": "The text link in 'holdToRevealContent3'"
},
"holdToRevealSRP": {
"message": "Appuyez longuement pour révéler PSR"
},
"holdToRevealSRPTitle": {
"message": "Protégez votre PSR"
},
"ignoreAll": {
"message": "Ignorer tout"
},
@ -1596,9 +1599,6 @@
"importNFTTokenIdToolTip": {
"message": "LID dun NFT est un identifiant unique puisquil ny a pas deux NFT identiques. Encore une fois, sur OpenSea, ce numéro se trouve dans la section « Détails ». Prenez-en note ou copiez-le dans votre presse-papiers."
},
"importNFTs": {
"message": "Importer des NFT"
},
"importSelectedTokens": {
"message": "Voulez-vous importer les jetons sélectionnés ?"
},
@ -1857,9 +1857,6 @@
"lock": {
"message": "Déconnexion"
},
"lockTimeTooGreat": {
"message": "Le temps de verrouillage est trop important"
},
"logo": {
"message": "Logo $1",
"description": "$1 is the name of the ticker"
@ -1953,9 +1950,6 @@
"mismatchedRpcUrl": {
"message": "Selon nos informations, la valeur de lURL RPC soumise ne correspond pas à un fournisseur connu pour cet ID de chaîne."
},
"missingNFT": {
"message": "Vous ne voyez pas votre NFT?"
},
"missingSetting": {
"message": "Vous ne trouvez pas un paramètre ?"
},
@ -2162,9 +2156,6 @@
"noConversionRateAvailable": {
"message": "Aucun taux de conversion disponible"
},
"noNFTs": {
"message": "Aucun NFT pour le moment"
},
"noSnaps": {
"message": "Aucun Snap installé"
},
@ -3333,12 +3324,63 @@
},
"srpPasteFailedTooManyWords": {
"message": "Le collage a échoué parce que la phrase contenait plus de 24 mots. Une phrase secrète de récupération peut contenir un maximum de 24 mots.",
"description": "Description of SRP paste erorr when the pasted content has too many words"
"description": "Description of SRP paste error when the pasted content has too many words"
},
"srpPasteTip": {
"message": "Vous pouvez coller toute votre phrase de récupération secrète dans nimporte quel champ",
"description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly."
},
"srpSecurityQuizGetStarted": {
"message": "Commencer"
},
"srpSecurityQuizIntroduction": {
"message": "Pour révéler votre Phrase secrète de récupération, vous devez répondre correctement à deux questions"
},
"srpSecurityQuizQuestionOneQuestion": {
"message": "Si vous perdez votre Phrase secrète de récupération, MetaMask..."
},
"srpSecurityQuizQuestionOneRightAnswer": {
"message": "Ne pourra pas vous aider"
},
"srpSecurityQuizQuestionOneRightAnswerDescription": {
"message": "Gravez-la sur une plaque en métal ou inscrivez-la sur plusieurs bouts de papier et cachez-les dans différents endroits secrets pour ne jamais la perdre. Si vous la perdez, il n'y a aucun moyen de la récupérer."
},
"srpSecurityQuizQuestionOneRightAnswerTitle": {
"message": "En effet ! Personne ne peut vous aider à récupérer votre Phrase secrète de récupération."
},
"srpSecurityQuizQuestionOneWrongAnswer": {
"message": "Pourra la récupérer pour vous"
},
"srpSecurityQuizQuestionOneWrongAnswerDescription": {
"message": "Personne ne peut vous aider à récupérer votre phrase secrète de récupération si jamais vous la perdez."
},
"srpSecurityQuizQuestionOneWrongAnswerTitle": {
"message": "C'est faux ! Personne ne peut vous aider à récupérer votre Phrase secrète de récupération."
},
"srpSecurityQuizQuestionTwoQuestion": {
"message": "Si un membre du service d'assistance ou toute autre personne vous demande votre Phrase secrète de récupération..."
},
"srpSecurityQuizQuestionTwoRightAnswer": {
"message": "Ne la lui fournissez pas, car cette personne essaie de vous arnaquer."
},
"srpSecurityQuizQuestionTwoRightAnswerDescription": {
"message": "Toute personne qui vous demande votre phrase secrète de récupération, que ce soit pour des raisons de sécurité ou autre, essaie de vous arnaquer."
},
"srpSecurityQuizQuestionTwoRightAnswerTitle": {
"message": "C'est exact ! Vous ne devez jamais partager votre Phrase secrète de récupération avec qui que ce soit."
},
"srpSecurityQuizQuestionTwoWrongAnswer": {
"message": "Vous devez la lui fournir"
},
"srpSecurityQuizQuestionTwoWrongAnswerDescription": {
"message": "Toute personne qui vous demande votre phrase secrète de récupération, que ce soit pour des raisons de sécurité ou autre, essaie de vous arnaquer."
},
"srpSecurityQuizQuestionTwoWrongAnswerTitle": {
"message": "C'est faux ! Vous ne devez jamais partager votre Phrase secrète de récupération avec qui que ce soit."
},
"srpSecurityQuizTitle": {
"message": "Quiz sur la sécurité"
},
"srpToggleShow": {
"message": "Afficher / Masquer ce mot de la phrase de récupération secrète",
"description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase"

View File

@ -211,9 +211,6 @@
"delete": {
"message": "מחיקה"
},
"deleteAccount": {
"message": "מחק חשבון"
},
"deleteNetwork": {
"message": "למחוק את הרשת?"
},

View File

@ -911,9 +911,6 @@
"delete": {
"message": "हटाएँ"
},
"deleteAccount": {
"message": "अकाउंट हटाएं"
},
"deleteNetwork": {
"message": "नेटवर्क हटाएं?"
},
@ -1562,6 +1559,12 @@
"message": "लेकिन फिशर कर सकते हैं।",
"description": "The text link in 'holdToRevealContent3'"
},
"holdToRevealSRP": {
"message": "SRP दिखाने के लिए होल्ड करें"
},
"holdToRevealSRPTitle": {
"message": "अपना SRP सुरक्षित रखें"
},
"ignoreAll": {
"message": "सभी को अनदेखा करें"
},
@ -1596,9 +1599,6 @@
"importNFTTokenIdToolTip": {
"message": "किसी एनएफटी की आईडी एक विशिष्ट पहचानकर्ता है क्योंकि कोई भी दो एनएफटी एक जैसे नहीं होते हैं। फिर से, OpenSea पर यह संख्या 'डिटेल्स' के नीचे होगी। इसे नोट कर लें या अपने क्लिपबोर्ड पर कॉपी कर लें।"
},
"importNFTs": {
"message": "NFT आयात करें"
},
"importSelectedTokens": {
"message": "चयनित टोकन आयात करें?"
},
@ -1857,9 +1857,6 @@
"lock": {
"message": "लॉक"
},
"lockTimeTooGreat": {
"message": "लॉक समय बहुत अधिक है"
},
"logo": {
"message": "$1 लोगो",
"description": "$1 is the name of the ticker"
@ -1953,9 +1950,6 @@
"mismatchedRpcUrl": {
"message": "हमारे रिकॉर्ड के अनुसार, सबमिट किया गया RPC-URL मान इस चेन आईडी के लिए किसी ज्ञात प्रदाता से मेल नहीं खाता।"
},
"missingNFT": {
"message": "अपना NFT नहीं देख रहे हैं?"
},
"missingSetting": {
"message": "सेटिंग नहीं मिल पाया?"
},
@ -2162,9 +2156,6 @@
"noConversionRateAvailable": {
"message": "कोई भी रूपांतरण दर उपलब्ध नहीं है"
},
"noNFTs": {
"message": "अभी तक कोई NFT नहीं"
},
"noSnaps": {
"message": "कोई स्नैप इंस्टाल नहीं किया गया"
},
@ -3333,12 +3324,63 @@
},
"srpPasteFailedTooManyWords": {
"message": "पेस्ट विफल हुआ क्योंकि उसमें 24 से ज़्यादा शब्द हैं। सीक्रेट रिकवरी फ़्रेज़ में अधिकतम 24 शब्द हो सकते हैं।",
"description": "Description of SRP paste erorr when the pasted content has too many words"
"description": "Description of SRP paste error when the pasted content has too many words"
},
"srpPasteTip": {
"message": "आप अपना पूरा सीक्रेट रिकवरी फ़्रेज किसी भी फ़ील्ड में पेस्ट कर सकते हैं",
"description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly."
},
"srpSecurityQuizGetStarted": {
"message": "शुरू करें"
},
"srpSecurityQuizIntroduction": {
"message": "अपना सीक्रेट रिकवरी फ्रेज़ प्रकट करने के लिए, आपको दो प्रश्नों का सही उत्तर देना होगा"
},
"srpSecurityQuizQuestionOneQuestion": {
"message": "यदि आप अपना सीक्रेट रिकवरी फ्रेज़ खो देते हैं, तो MetaMask..."
},
"srpSecurityQuizQuestionOneRightAnswer": {
"message": "आपकी मदद नहीं कर सकता"
},
"srpSecurityQuizQuestionOneRightAnswerDescription": {
"message": "इसे लिख लें, इसे किसी धातु पर उकेर दें, या इसे कई गुप्त स्थानों पर रखें ताकि आप इसे कभी न खोएं। यदि आप इसे खो देते हैं, तो यह हमेशा के लिए चला जाता है।"
},
"srpSecurityQuizQuestionOneRightAnswerTitle": {
"message": "सही! आपके सीक्रेट रिकवरी फ्रेज़ को वापस पाने में कोई भी सहायता नहीं कर सकता"
},
"srpSecurityQuizQuestionOneWrongAnswer": {
"message": "आपके लिए इसे वापस ला सकते हैं"
},
"srpSecurityQuizQuestionOneWrongAnswerDescription": {
"message": "यदि आप अपना सीक्रेट रिकवरी फ्रेज़ खो देते हैं, तो यह हमेशा के लिए चला जाता है। इसे वापस पाने में कोई भी आपकी मदद नहीं कर सकता, चाहे वे कुछ भी कहें।"
},
"srpSecurityQuizQuestionOneWrongAnswerTitle": {
"message": "गलत! आपके सीक्रेट रिकवरी फ्रेज़ को वापस पाने में कोई भी सहायता नहीं कर सकता"
},
"srpSecurityQuizQuestionTwoQuestion": {
"message": "यदि कोई, यहां तक कि एक सहायक एजेंट भी, आपका सीक्रेट रिकवरी फ्रेज़ मांगता है..."
},
"srpSecurityQuizQuestionTwoRightAnswer": {
"message": "तो आपके साथ धोखा किया जा रहा है"
},
"srpSecurityQuizQuestionTwoRightAnswerDescription": {
"message": "आपके सीक्रेट रिकवरी फ्रेज़ की आवश्यकता का दावा करने वाला कोई भी व्यक्ति आपसे झूठ बोल रहा है। यदि आप इसे उनके साथ साझा करते हैं, तो वे आपकी संपत्ति चुरा लेंगे।"
},
"srpSecurityQuizQuestionTwoRightAnswerTitle": {
"message": "सही! अपना सीक्रेट रिकवरी फ्रेज़ साझा करना कभी भी अच्छा विचार नहीं है"
},
"srpSecurityQuizQuestionTwoWrongAnswer": {
"message": "आपको उन्हें यह देना चाहिए"
},
"srpSecurityQuizQuestionTwoWrongAnswerDescription": {
"message": "आपके सीक्रेट रिकवरी फ्रेज़ की आवश्यकता का दावा करने वाला कोई भी व्यक्ति आपसे झूठ बोल रहा है। यदि आप इसे उनके साथ साझा करते हैं, तो वे आपकी संपत्तियां चुरा लेंगे।"
},
"srpSecurityQuizQuestionTwoWrongAnswerTitle": {
"message": "नहीं! अपने सीक्रेट रिकवरी फ्रेज़ को कभी भी किसी के साथ साझा न करें"
},
"srpSecurityQuizTitle": {
"message": "सुरक्षा प्रश्नोत्तरी"
},
"srpToggleShow": {
"message": "सीक्रेट रिकवरी फ़्रेज का ये शब्द दिखाएं/छुपाएं",
"description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase"

View File

@ -211,9 +211,6 @@
"delete": {
"message": "Izbriši"
},
"deleteAccount": {
"message": "Izbriši račun"
},
"deleteNetwork": {
"message": "Izbrisati mrežu?"
},

View File

@ -211,9 +211,6 @@
"delete": {
"message": "Törlés"
},
"deleteAccount": {
"message": "Fiók törlése"
},
"deleteNetwork": {
"message": "Törli a hálózatot?"
},

View File

@ -911,9 +911,6 @@
"delete": {
"message": "Hapus"
},
"deleteAccount": {
"message": "Hapus akun"
},
"deleteNetwork": {
"message": "Hapus jaringan?"
},
@ -1562,6 +1559,12 @@
"message": "tetapi penipu akan mencoba memintanya.",
"description": "The text link in 'holdToRevealContent3'"
},
"holdToRevealSRP": {
"message": "Tahan untuk mengungkap FPR"
},
"holdToRevealSRPTitle": {
"message": "Jaga keamanan FPR Anda"
},
"ignoreAll": {
"message": "Abaikan semua"
},
@ -1596,9 +1599,6 @@
"importNFTTokenIdToolTip": {
"message": "ID NFT merupakan pengenal unik karena tidak ada dua NFT yang sama. Sekali lagi, angka ini berada di bawah 'Detail' pada OpenSea. Catat atau salin ke papan klip."
},
"importNFTs": {
"message": "Impor NFT"
},
"importSelectedTokens": {
"message": "Impor token yang dipilih?"
},
@ -1857,9 +1857,6 @@
"lock": {
"message": "Kunci"
},
"lockTimeTooGreat": {
"message": "Lock time terlalu besar"
},
"logo": {
"message": "Logo $1",
"description": "$1 is the name of the ticker"
@ -1953,9 +1950,6 @@
"mismatchedRpcUrl": {
"message": "Menurut catatan kami, nilai URL RPC yang dikirimkan tidak sesuai dengan penyedia yang dikenal untuk ID rantai ini."
},
"missingNFT": {
"message": "Tidak melihat NFT Anda?"
},
"missingSetting": {
"message": "Tidak dapat menemukan pengaturan?"
},
@ -2162,9 +2156,6 @@
"noConversionRateAvailable": {
"message": "Nilai konversi tidak tersedia"
},
"noNFTs": {
"message": "Belum ada NFT"
},
"noSnaps": {
"message": "Belum ada Snap yang diinstal"
},
@ -3333,12 +3324,63 @@
},
"srpPasteFailedTooManyWords": {
"message": "Gagal ditempel karena memuat lebih dari 24 kata. Frasa pemulihan rahasia dapat memuat maksimum 24 kata.",
"description": "Description of SRP paste erorr when the pasted content has too many words"
"description": "Description of SRP paste error when the pasted content has too many words"
},
"srpPasteTip": {
"message": "Anda bisa menempelkan seluruh frasa pemulihan rahasia ke bagian mana pun",
"description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly."
},
"srpSecurityQuizGetStarted": {
"message": "Mulai"
},
"srpSecurityQuizIntroduction": {
"message": "Untuk mengungkapkan Frasa Pemulihan Rahasia, Anda perlu menjawab dua pertanyaan dengan benar"
},
"srpSecurityQuizQuestionOneQuestion": {
"message": "Jika Anda kehilangan Frasa Pemulihan Rahasia, MetaMask..."
},
"srpSecurityQuizQuestionOneRightAnswer": {
"message": "Tidak dapat membantu Anda"
},
"srpSecurityQuizQuestionOneRightAnswerDescription": {
"message": "Catat, ukir pada logam, atau simpan di beberapa tempat rahasia agar Anda tidak pernah kehilangan. Jika Anda kehilangan, maka akan hilang selamanya."
},
"srpSecurityQuizQuestionOneRightAnswerTitle": {
"message": "Benar! Tidak ada yang dapat membantu mengembalikan Frasa Pemulihan Rahasia Anda"
},
"srpSecurityQuizQuestionOneWrongAnswer": {
"message": "Dapat mengembalikannya untuk Anda"
},
"srpSecurityQuizQuestionOneWrongAnswerDescription": {
"message": "Jika Anda kehilangan Frasa Pemulihan Rahasia, maka akan hilang selamanya. Tidak ada yang dapat membantu Anda mengembalikannya, apa pun yang mereka katakan."
},
"srpSecurityQuizQuestionOneWrongAnswerTitle": {
"message": "Salah! Tidak ada yang dapat membantu mengembalikan Frasa Pemulihan Rahasia Anda"
},
"srpSecurityQuizQuestionTwoQuestion": {
"message": "Jika ada yang menanyakan Frasa Pemulihan Rahasia Anda, bahkan agen pendukung,..."
},
"srpSecurityQuizQuestionTwoRightAnswer": {
"message": "Anda ditipu"
},
"srpSecurityQuizQuestionTwoRightAnswerDescription": {
"message": "Siapa pun yang mengaku membutuhkan Frasa Pemulihan Rahasia, mereka berbohong kepada Anda. Jika membaginya, mereka akan mencuri aset Anda."
},
"srpSecurityQuizQuestionTwoRightAnswerTitle": {
"message": "Benar! Membagikan Frasa Pemulihan Rahasia bukanlah ide yang baik"
},
"srpSecurityQuizQuestionTwoWrongAnswer": {
"message": "Anda harus memberikannya kepada mereka"
},
"srpSecurityQuizQuestionTwoWrongAnswerDescription": {
"message": "Siapa pun yang mengaku membutuhkan Frasa Pemulihan Rahasia, mereka berbohong kepada Anda. Jika membaginya, mereka akan mencuri aset Anda."
},
"srpSecurityQuizQuestionTwoWrongAnswerTitle": {
"message": "Tidak! Jangan pernah membagikan Frasa Pemulihan Rahasia kepada siapa pun"
},
"srpSecurityQuizTitle": {
"message": "Kuis keamanan"
},
"srpToggleShow": {
"message": "Tampilkan/Sembunyikan kata dari frasa pemulihan rahasia ini",
"description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase"

View File

@ -716,9 +716,6 @@
"delete": {
"message": "Elimina"
},
"deleteAccount": {
"message": "Cancella account"
},
"deleteNetwork": {
"message": "Cancella la rete?"
},
@ -1117,9 +1114,6 @@
"lock": {
"message": "Disconnetti"
},
"lockTimeTooGreat": {
"message": "Tempo di inattività troppo lungo"
},
"mainnet": {
"message": "Rete Ethereum Principale"
},

View File

@ -911,9 +911,6 @@
"delete": {
"message": "削除"
},
"deleteAccount": {
"message": "アカウントを削除"
},
"deleteNetwork": {
"message": "ネットワークを削除しますか?"
},
@ -1562,6 +1559,12 @@
"message": "もし尋ねられた場合はフィッシング詐欺の可能性があります。",
"description": "The text link in 'holdToRevealContent3'"
},
"holdToRevealSRP": {
"message": "長押ししてSRPを表示"
},
"holdToRevealSRPTitle": {
"message": "SRPは安全に保管してください"
},
"ignoreAll": {
"message": "すべて無視"
},
@ -1596,9 +1599,6 @@
"importNFTTokenIdToolTip": {
"message": "NFT の ID は一意の識別子で、同じ NFT は 2 つとして存在しません。前述の通り、OpenSea ではこの番号は「詳細」に表示されます。この ID を書き留めるか、クリップボードにコピーしてください。"
},
"importNFTs": {
"message": "NFTをインポート"
},
"importSelectedTokens": {
"message": "選択したトークンをインポートしますか?"
},
@ -1857,9 +1857,6 @@
"lock": {
"message": "ロック"
},
"lockTimeTooGreat": {
"message": "ロック時間が長すぎます"
},
"logo": {
"message": "$1 ロゴ",
"description": "$1 is the name of the ticker"
@ -1953,9 +1950,6 @@
"mismatchedRpcUrl": {
"message": "弊社の記録によると、送信された RPC URL の値がこのチェーン ID の既知のプロバイダーと一致しません。"
},
"missingNFT": {
"message": "NFTが見当たりませんか"
},
"missingSetting": {
"message": "設定が見つかりませんか?"
},
@ -2162,9 +2156,6 @@
"noConversionRateAvailable": {
"message": "利用可能な換算レートがありません"
},
"noNFTs": {
"message": "NFTはまだありません"
},
"noSnaps": {
"message": "スナップがインストールされていません"
},
@ -3333,12 +3324,63 @@
},
"srpPasteFailedTooManyWords": {
"message": "24 を超える単語が含まれていたため、貼り付けに失敗しました。秘密のリカバリーフレーズは 24 語までです。",
"description": "Description of SRP paste erorr when the pasted content has too many words"
"description": "Description of SRP paste error when the pasted content has too many words"
},
"srpPasteTip": {
"message": "秘密のリカバリーフレーズ全体をいずれかのフィールドに張り付けできます。",
"description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly."
},
"srpSecurityQuizGetStarted": {
"message": "開始"
},
"srpSecurityQuizIntroduction": {
"message": "秘密のリカバリーフレーズを表示するには、2 つの質問に正しく答える必要があります。"
},
"srpSecurityQuizQuestionOneQuestion": {
"message": "秘密のリカバリーフレーズをなくした場合、MetaMask は..."
},
"srpSecurityQuizQuestionOneRightAnswer": {
"message": "どうすることもできません"
},
"srpSecurityQuizQuestionOneRightAnswerDescription": {
"message": "書き留めたり金属に掘ったり、いくつかの秘密の場所に保管したりして、絶対になくさないようにしてください。なくした場合、一生戻ってきません。"
},
"srpSecurityQuizQuestionOneRightAnswerTitle": {
"message": "正解です!秘密のリカバリーフレーズは誰にも取り戻すことができません"
},
"srpSecurityQuizQuestionOneWrongAnswer": {
"message": "それを取り戻すことができます"
},
"srpSecurityQuizQuestionOneWrongAnswerDescription": {
"message": "秘密のリカバリーフレーズをなくした場合、一生戻ってきません。誰が何と言おうと、誰にも取り戻すことはできません。"
},
"srpSecurityQuizQuestionOneWrongAnswerTitle": {
"message": "不正解!秘密のリカバリーフレーズは誰にも取り戻せません"
},
"srpSecurityQuizQuestionTwoQuestion": {
"message": "誰かに秘密のリカバリーフレーズを尋ねられたら、それがサポート担当者であっても..."
},
"srpSecurityQuizQuestionTwoRightAnswer": {
"message": "あなたは騙されようとしています"
},
"srpSecurityQuizQuestionTwoRightAnswerDescription": {
"message": "秘密のリカバリーフレーズが必要だと言われたら、それは嘘です。教えてしまったら資産を盗まれます。"
},
"srpSecurityQuizQuestionTwoRightAnswerTitle": {
"message": "正解です!秘密のリカバリーフレーズは決して誰にも教えてはいけません"
},
"srpSecurityQuizQuestionTwoWrongAnswer": {
"message": "教えるべきです"
},
"srpSecurityQuizQuestionTwoWrongAnswerDescription": {
"message": "秘密のリカバリーフレーズが必要だと言われたら、それは嘘です。教えてしまったら資産を盗まれます。"
},
"srpSecurityQuizQuestionTwoWrongAnswerTitle": {
"message": "不正解!秘密のリカバリーフレーズは決して誰にも教えないでください"
},
"srpSecurityQuizTitle": {
"message": "セキュリティの質問"
},
"srpToggleShow": {
"message": "秘密のリカバリーフレーズのこの単語を表示・非表示",
"description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase"

View File

@ -211,9 +211,6 @@
"delete": {
"message": "ಅಳಿಸಿ"
},
"deleteAccount": {
"message": "ಖಾತೆಯನ್ನು ಅಳಿಸಿ"
},
"deleteNetwork": {
"message": "ನೆಟ್‌ವರ್ಕ್ ಅಳಿಸುವುದೇ?"
},

View File

@ -911,9 +911,6 @@
"delete": {
"message": "삭제"
},
"deleteAccount": {
"message": "계정 삭제"
},
"deleteNetwork": {
"message": "네트워크를 삭제할까요?"
},
@ -1562,6 +1559,12 @@
"message": "오히려 피싱 사기꾼들이 요구할 수 있으니 주의가 필요합니다.",
"description": "The text link in 'holdToRevealContent3'"
},
"holdToRevealSRP": {
"message": "눌러서 SRP 정보를 확인하세요"
},
"holdToRevealSRPTitle": {
"message": "SRP 정보를 안전하게 보관하세요"
},
"ignoreAll": {
"message": "모두 무시"
},
@ -1596,9 +1599,6 @@
"importNFTTokenIdToolTip": {
"message": "NFT의 ID는 고유한 식별자이므로 동일한 NFT는 존재하지 않습니다. 다시 말하지만, OpenSea에서 이 번호는 '세부 정보(Details)'에서 찾아볼 수 있습니다. 이를 기록하거나 클립보드에 복사해 두세요."
},
"importNFTs": {
"message": "NFT 가져오기"
},
"importSelectedTokens": {
"message": "선택한 토큰을 불러올까요?"
},
@ -1857,9 +1857,6 @@
"lock": {
"message": "잠금"
},
"lockTimeTooGreat": {
"message": "잠금 시간이 너무 깁니다"
},
"logo": {
"message": "$1 로고",
"description": "$1 is the name of the ticker"
@ -1953,9 +1950,6 @@
"mismatchedRpcUrl": {
"message": "기록에 따르면 제출한 RPC URL 값이 이 체인 ID의 알려진 공급업체와 일치하지 않습니다."
},
"missingNFT": {
"message": "NFT가 보이지 않나요?"
},
"missingSetting": {
"message": "설정을 찾으세요?"
},
@ -2162,9 +2156,6 @@
"noConversionRateAvailable": {
"message": "사용 가능한 환율 없음"
},
"noNFTs": {
"message": "아직 NFT가 없음"
},
"noSnaps": {
"message": "설치된 스냅이 없습니다"
},
@ -3333,12 +3324,63 @@
},
"srpPasteFailedTooManyWords": {
"message": "단어가 24개를 초과하여 붙여넣기에 실패했습니다. 비밀 복구 구문은 24개 이하의 단어로 이루어집니다.",
"description": "Description of SRP paste erorr when the pasted content has too many words"
"description": "Description of SRP paste error when the pasted content has too many words"
},
"srpPasteTip": {
"message": "비밀 복구 구문 전체를 아무 입력란에 붙여넣을 수 있습니다",
"description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly."
},
"srpSecurityQuizGetStarted": {
"message": "시작하기"
},
"srpSecurityQuizIntroduction": {
"message": "비밀 복구 구문을 찾으려면 두 가지 질문에 올바르게 답해야 합니다"
},
"srpSecurityQuizQuestionOneQuestion": {
"message": "비밀 복구 구문을 분실하시면 MetaMask가..."
},
"srpSecurityQuizQuestionOneRightAnswer": {
"message": "도와드릴 수 없습니다"
},
"srpSecurityQuizQuestionOneRightAnswerDescription": {
"message": "따라서 이를 적거나, 금속 등에 새기거나, 여러 비밀 장소에 보관하여 절대로 잃어버리지 않도록 하세요. 한 번 잃어버리면 영원히 찾을 수 없습니다."
},
"srpSecurityQuizQuestionOneRightAnswerTitle": {
"message": "맞습니다! 아무도 본인의 비밀 복구 구문을 복구할 수 없습니다"
},
"srpSecurityQuizQuestionOneWrongAnswer": {
"message": "찾아드릴 수 있습니다"
},
"srpSecurityQuizQuestionOneWrongAnswerDescription": {
"message": "비밀 복구 구문은 한 번 잃어버리면 영원히 찾을 수 없습니다. 누가 뭐라고 해도 아무도 이를 찾아드리지 못합니다."
},
"srpSecurityQuizQuestionOneWrongAnswerTitle": {
"message": "아닙니다! 아무도 본인의 비밀 복구 구문을 복구할 수 없습니다"
},
"srpSecurityQuizQuestionTwoQuestion": {
"message": "누군가, 심지어 고객 센터 직원이라고 해도 여러분의 비밀 복구 구문을 물어본다면..."
},
"srpSecurityQuizQuestionTwoRightAnswer": {
"message": "이는 반드시 사기입니다"
},
"srpSecurityQuizQuestionTwoRightAnswerDescription": {
"message": "비밀 복구 구문이 필요하다고 하는 사람은 모두 거짓말쟁이입니다. 그런 자들과 비밀 복구 구문을 공유하면 자산을 도둑맞게 됩니다."
},
"srpSecurityQuizQuestionTwoRightAnswerTitle": {
"message": "맞습니다! 비밀 복구 구문은 아무와도 공유하면 안 됩니다"
},
"srpSecurityQuizQuestionTwoWrongAnswer": {
"message": "주어야 합니다"
},
"srpSecurityQuizQuestionTwoWrongAnswerDescription": {
"message": "비밀 복구 구문이 필요하다고 하는 사람은 모두 거짓말쟁이입니다. 그런 자들과 비밀 복구 구문을 공유하면 자산을 도둑맞게 됩니다."
},
"srpSecurityQuizQuestionTwoWrongAnswerTitle": {
"message": "맞습니다! 비밀 복구 구문은 절대로 아무와도 공유하면 안 됩니다"
},
"srpSecurityQuizTitle": {
"message": "보안 퀴즈"
},
"srpToggleShow": {
"message": "비밀 복구 구문 중에서 이 단어 공개하기/숨기기",
"description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase"

View File

@ -211,9 +211,6 @@
"delete": {
"message": "Ištrinti"
},
"deleteAccount": {
"message": "Šalinti paskyrą"
},
"deleteNetwork": {
"message": "Panaikinti tinklą?"
},

View File

@ -211,9 +211,6 @@
"delete": {
"message": "Dzēst"
},
"deleteAccount": {
"message": "Dzēst kontu"
},
"deleteNetwork": {
"message": "Dzēst tīklu?"
},

View File

@ -211,9 +211,6 @@
"delete": {
"message": "Padam"
},
"deleteAccount": {
"message": "Hapus Akaun"
},
"deleteNetwork": {
"message": "Padamkan Rangkaian?"
},

View File

@ -208,9 +208,6 @@
"delete": {
"message": "Slett"
},
"deleteAccount": {
"message": "Slett konto "
},
"deleteNetwork": {
"message": "Slette nettverk? "
},

View File

@ -406,9 +406,6 @@
"delete": {
"message": "I-delete"
},
"deleteAccount": {
"message": "I-delete ang Account"
},
"deleteNetwork": {
"message": "I-delete ang Network?"
},
@ -816,9 +813,6 @@
"lock": {
"message": "I-lock"
},
"lockTimeTooGreat": {
"message": "Masyadong matagal ang oras ng pag-lock"
},
"mainnet": {
"message": "Ethereum Mainnet"
},

View File

@ -211,9 +211,6 @@
"delete": {
"message": "Usuń"
},
"deleteAccount": {
"message": "Usuń konto"
},
"deleteNetwork": {
"message": "Usunąć sieć?"
},

View File

@ -911,9 +911,6 @@
"delete": {
"message": "Excluir"
},
"deleteAccount": {
"message": "Excluir conta"
},
"deleteNetwork": {
"message": "Excluir rede?"
},
@ -1562,6 +1559,12 @@
"message": "mas os phishers talvez solicitem.",
"description": "The text link in 'holdToRevealContent3'"
},
"holdToRevealSRP": {
"message": "Mantenha pressionado para revelar FRS"
},
"holdToRevealSRPTitle": {
"message": "Mantenha sua FRS protegida"
},
"ignoreAll": {
"message": "Ignorar tudo"
},
@ -1596,9 +1599,6 @@
"importNFTTokenIdToolTip": {
"message": "O ID de um NFT é um identificador único, pois não há dois NFTs iguais. Novamente, na OpenSea, esse número se encontra em \"Detalhes\". Anote-o ou copie-o para sua área de transferência."
},
"importNFTs": {
"message": "Importar NFTs"
},
"importSelectedTokens": {
"message": "Importar tokens selecionados?"
},
@ -1857,9 +1857,6 @@
"lock": {
"message": "Sair"
},
"lockTimeTooGreat": {
"message": "O tempo de bloqueio é longo demais"
},
"logo": {
"message": "Logotipo do $1",
"description": "$1 is the name of the ticker"
@ -1953,9 +1950,6 @@
"mismatchedRpcUrl": {
"message": "De acordo com os nossos registros, o valor da URL da RPC enviado não corresponde a um provedor conhecido da ID desta cadeia."
},
"missingNFT": {
"message": "Não está vendo o seu NFT?"
},
"missingSetting": {
"message": "Não consegue encontrar uma configuração?"
},
@ -2162,9 +2156,6 @@
"noConversionRateAvailable": {
"message": "Não há uma taxa de conversão disponível"
},
"noNFTs": {
"message": "Nenhum NFT até agora"
},
"noSnaps": {
"message": "Nenhum snap instalado"
},
@ -3333,12 +3324,63 @@
},
"srpPasteFailedTooManyWords": {
"message": "A função colar falhou porque continha mais de 24 palavras. Uma frase secreta de recuperação pode ter no máximo 24 palavras.",
"description": "Description of SRP paste erorr when the pasted content has too many words"
"description": "Description of SRP paste error when the pasted content has too many words"
},
"srpPasteTip": {
"message": "Você pode colar a sua frase secreta de recuperação inteira em qualquer campo",
"description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly."
},
"srpSecurityQuizGetStarted": {
"message": "Começar"
},
"srpSecurityQuizIntroduction": {
"message": "Para revelar sua Frase de Recuperação Secreta, você precisa responder corretamente duas perguntas"
},
"srpSecurityQuizQuestionOneQuestion": {
"message": "Se você perder sua Frase de Recuperação Secreta, a MetaMask..."
},
"srpSecurityQuizQuestionOneRightAnswer": {
"message": "Não poderá ajudar"
},
"srpSecurityQuizQuestionOneRightAnswerDescription": {
"message": "Anote-a, grave em metal ou guarde-a em diversos lugares secretos para que nunca a perca. Se perdê-la, é para sempre."
},
"srpSecurityQuizQuestionOneRightAnswerTitle": {
"message": "Certo! Ninguém pode ajudar a recuperar sua Frase de Recuperação Secreta"
},
"srpSecurityQuizQuestionOneWrongAnswer": {
"message": "Poderá recuperá-la para você"
},
"srpSecurityQuizQuestionOneWrongAnswerDescription": {
"message": "Se você perder sua Frase de Recuperação Secreta, é para sempre. Ninguém consegue ajudar a recuperá-la, não importa o que digam."
},
"srpSecurityQuizQuestionOneWrongAnswerTitle": {
"message": "Errado! Ninguém consegue recuperar sua Frase de Recuperação Secreta"
},
"srpSecurityQuizQuestionTwoQuestion": {
"message": "Se alguém, até mesmo um atendente do suporte, pedir sua Frase de Recuperação Secreta..."
},
"srpSecurityQuizQuestionTwoRightAnswer": {
"message": "Você estará sendo vítima de um golpe"
},
"srpSecurityQuizQuestionTwoRightAnswerDescription": {
"message": "Qualquer pessoa que afirme precisar da sua Frase de Recuperação Secreta está mentindo. Se você compartilhar com ela, seus ativos serão roubados."
},
"srpSecurityQuizQuestionTwoRightAnswerTitle": {
"message": "Correto! Compartilhar sua Frase de Recuperação Secreta nunca é uma boa ideia"
},
"srpSecurityQuizQuestionTwoWrongAnswer": {
"message": "Você deverá revelar"
},
"srpSecurityQuizQuestionTwoWrongAnswerDescription": {
"message": "Qualquer pessoa que afirme precisar da sua Frase de Recuperação Secreta está mentindo. Se você compartilhar com ela, seus ativos serão roubados."
},
"srpSecurityQuizQuestionTwoWrongAnswerTitle": {
"message": "Não! Não compartilhe sua Frase de Recuperação Secreta com ninguém, nunca"
},
"srpSecurityQuizTitle": {
"message": "Quiz de segurança"
},
"srpToggleShow": {
"message": "Mostrar/Ocultar esta palavra da frase secreta de recuperação",
"description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase"

View File

@ -581,9 +581,6 @@
"delete": {
"message": "Excluir"
},
"deleteAccount": {
"message": "Excluir conta"
},
"deleteNetwork": {
"message": "Excluir rede?"
},
@ -1073,9 +1070,6 @@
"importMyWallet": {
"message": "Importar minha carteira"
},
"importNFTs": {
"message": "Importar NFTs"
},
"importTokenQuestion": {
"message": "Importar token?"
},
@ -1269,9 +1263,6 @@
"lock": {
"message": "Bloquear"
},
"lockTimeTooGreat": {
"message": "O tempo de bloqueio é longo demais"
},
"low": {
"message": "Baixa"
},
@ -1336,9 +1327,6 @@
"message": "verifique os detalhes da rede",
"description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key."
},
"missingNFT": {
"message": "Não está vendo o seu NFT?"
},
"mustSelectOne": {
"message": "Selecione pelo menos 1 token."
},
@ -1471,9 +1459,6 @@
"noConversionRateAvailable": {
"message": "Não há uma taxa de conversão disponível"
},
"noNFTs": {
"message": "Ainda não há nenhum NFT"
},
"noTransactions": {
"message": "Você não tem transações"
},

View File

@ -211,9 +211,6 @@
"delete": {
"message": "Șterge"
},
"deleteAccount": {
"message": "Ștergeți cont"
},
"deleteNetwork": {
"message": "Ștergeți rețeaua?"
},

View File

@ -911,9 +911,6 @@
"delete": {
"message": "Удалить"
},
"deleteAccount": {
"message": "Удалить счет"
},
"deleteNetwork": {
"message": "Удалить сеть?"
},
@ -1562,6 +1559,12 @@
"message": "но злоумышленники-фишеры могут.",
"description": "The text link in 'holdToRevealContent3'"
},
"holdToRevealSRP": {
"message": "Удерживайте, чтобы показать СФВ"
},
"holdToRevealSRPTitle": {
"message": "Храните СФВ в безопасности"
},
"ignoreAll": {
"message": "Игнорировать все"
},
@ -1596,9 +1599,6 @@
"importNFTTokenIdToolTip": {
"message": "Идентификатор коллекционного актива является уникальным идентификатором, поскольку нет двух одинаковых NFT. Опять же, в OpenSea этот номер находится в разделе «Подробности». Запишите его или скопируйте в буфер обмена."
},
"importNFTs": {
"message": "Импорт NFT"
},
"importSelectedTokens": {
"message": "Импортировать выбранные токены?"
},
@ -1857,9 +1857,6 @@
"lock": {
"message": "Заблокировать"
},
"lockTimeTooGreat": {
"message": "Время блокировки слишком велико"
},
"logo": {
"message": "логотип $1",
"description": "$1 is the name of the ticker"
@ -1953,9 +1950,6 @@
"mismatchedRpcUrl": {
"message": "Согласно нашим записям, отправленное значение URL-адреса RPC не соответствует известному поставщику для этого идентификатора блокчейна."
},
"missingNFT": {
"message": "Не видите свои NFT?"
},
"missingSetting": {
"message": "Не удается найти настройку?"
},
@ -2162,9 +2156,6 @@
"noConversionRateAvailable": {
"message": "Нет доступного обменного курса"
},
"noNFTs": {
"message": "Пока нет NFT-токенов"
},
"noSnaps": {
"message": "Снапы не установлены"
},
@ -3333,12 +3324,63 @@
},
"srpPasteFailedTooManyWords": {
"message": "Не удалось вставить, так как он содержит более 24 слов. Секретная фраза для восстановления может содержать не более 24 слов.",
"description": "Description of SRP paste erorr when the pasted content has too many words"
"description": "Description of SRP paste error when the pasted content has too many words"
},
"srpPasteTip": {
"message": "Вы можете вставить всю свою секретную фразу для восстановления в любое поле",
"description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly."
},
"srpSecurityQuizGetStarted": {
"message": "Начать"
},
"srpSecurityQuizIntroduction": {
"message": "Чтобы увидеть свою секретную фразу для восстановления, вам нужно правильно ответить на два вопроса"
},
"srpSecurityQuizQuestionOneQuestion": {
"message": "Если вы потеряете свою секретную фразу для восстановления, MetaMask..."
},
"srpSecurityQuizQuestionOneRightAnswer": {
"message": "Не сможет вам помочь"
},
"srpSecurityQuizQuestionOneRightAnswerDescription": {
"message": "Запишите ее, выгравируйте ее на металле или храните в нескольких потайных местах, чтобы никогда не потерять. Если вы потеряете ее, она пропадет навсегда."
},
"srpSecurityQuizQuestionOneRightAnswerTitle": {
"message": "Правильно! Никто не может помочь вернуть вашу секретную фразу для восстановления"
},
"srpSecurityQuizQuestionOneWrongAnswer": {
"message": "Не сможет вернуть ее вам"
},
"srpSecurityQuizQuestionOneWrongAnswerDescription": {
"message": "Если вы потеряете свою секретную фразу для восстановления, она пропадет навсегда. Никто не может помочь вам вернуть ее, что бы кто ни говорил."
},
"srpSecurityQuizQuestionOneWrongAnswerTitle": {
"message": "Неправильно! Никто не может помочь вернуть вашу секретную фразу для восстановления"
},
"srpSecurityQuizQuestionTwoQuestion": {
"message": "Если кто-нибудь, даже представитель службы поддержки, попросит вашу секретную фразу для восстановления..."
},
"srpSecurityQuizQuestionTwoRightAnswer": {
"message": "Вас обманывают"
},
"srpSecurityQuizQuestionTwoRightAnswerDescription": {
"message": "Любой, кто утверждает, что ему нужна ваша секретная фраза для восстановления, лжет вам. Если вы сообщите эту фразу ему (ей), он(-а) украдет ваши активы."
},
"srpSecurityQuizQuestionTwoRightAnswerTitle": {
"message": "Правильно! Сообщать кому-либо своей секретную фразу для восстановления — это всегда плохая идея"
},
"srpSecurityQuizQuestionTwoWrongAnswer": {
"message": "Вы должны сообщите фразу ему (ей)"
},
"srpSecurityQuizQuestionTwoWrongAnswerDescription": {
"message": "Любой, кто утверждает, что ему нужна ваша секретная фраза для восстановления, лжет вам. Если вы сообщите эту фразу ему (ей), он(-а) украдет ваши активы."
},
"srpSecurityQuizQuestionTwoWrongAnswerTitle": {
"message": "Нет! Никогда никому не сообщайте никому свою секретную фразу для восстановления"
},
"srpSecurityQuizTitle": {
"message": "Тест по безопасности"
},
"srpToggleShow": {
"message": "Показать/скрыть это слово секретной фразы для восстановления",
"description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase"

View File

@ -205,9 +205,6 @@
"delete": {
"message": "Odstrániť"
},
"deleteAccount": {
"message": "Zmazať účet"
},
"deleteNetwork": {
"message": "Odstrániť sieť?"
},

View File

@ -211,9 +211,6 @@
"delete": {
"message": "Izbriši"
},
"deleteAccount": {
"message": "Izbriši račun"
},
"deleteNetwork": {
"message": "Izbrišem to omrežje?"
},

View File

@ -208,9 +208,6 @@
"delete": {
"message": "Избриши"
},
"deleteAccount": {
"message": "Obriši nalog"
},
"deleteNetwork": {
"message": "Da li želite da obrišete mrežu?"
},

View File

@ -205,9 +205,6 @@
"delete": {
"message": "Radera"
},
"deleteAccount": {
"message": "Radera konto"
},
"deleteNetwork": {
"message": "Radera nätverk?"
},

View File

@ -205,9 +205,6 @@
"delete": {
"message": "Futa"
},
"deleteAccount": {
"message": "Futa Akaunti"
},
"deleteNetwork": {
"message": "Futa Mtandao?"
},

View File

@ -911,9 +911,6 @@
"delete": {
"message": "I-delete"
},
"deleteAccount": {
"message": "I-delete ang Account"
},
"deleteNetwork": {
"message": "I-delete ang network?"
},
@ -1562,6 +1559,12 @@
"message": "ngunit maaring hingin ng mga phisher.",
"description": "The text link in 'holdToRevealContent3'"
},
"holdToRevealSRP": {
"message": "I-hold para ipakita ang SRP"
},
"holdToRevealSRPTitle": {
"message": "Panatilihing ligtas ang iyong SRP"
},
"ignoreAll": {
"message": "Huwag pansinin ang lahat"
},
@ -1596,9 +1599,6 @@
"importNFTTokenIdToolTip": {
"message": "Ang ID ng NFT ay isang natatanging pagkakakilanlan dahil walang dalawang NFT ang magkatulad. Muli, sa OpenSea ang numerong ito ay nasa ilalim ng 'Mga Detalye'. Itala ito, o kopyahin ito sa iyong clipboard."
},
"importNFTs": {
"message": "I-import ang mga NFT"
},
"importSelectedTokens": {
"message": "I-import ang mga napiling token?"
},
@ -1857,9 +1857,6 @@
"lock": {
"message": "I-lock"
},
"lockTimeTooGreat": {
"message": "Masyadong matagal ang oras ng pag-lock"
},
"logo": {
"message": "$1 logo",
"description": "$1 is the name of the ticker"
@ -1953,9 +1950,6 @@
"mismatchedRpcUrl": {
"message": "Ayon sa aming mga talaan, ang isinumiteng RPC URL value ay hindi tumutugma sa isang kilalang provider para sa chain ID na ito."
},
"missingNFT": {
"message": "Hindi makita ang NFT mo?"
},
"missingSetting": {
"message": "Hindi makahanap ng setting?"
},
@ -2162,9 +2156,6 @@
"noConversionRateAvailable": {
"message": "Hindi available ang rate ng conversion"
},
"noNFTs": {
"message": "Wala pang mga NFT"
},
"noSnaps": {
"message": "Walang mga Snap na naka-install"
},
@ -3333,12 +3324,63 @@
},
"srpPasteFailedTooManyWords": {
"message": "Nabigong i-paste dahil naglalaman ito ng higit sa 24 na salita. Ang secret recovery phrase ay mayroong hanggang 24 na salita lamang.",
"description": "Description of SRP paste erorr when the pasted content has too many words"
"description": "Description of SRP paste error when the pasted content has too many words"
},
"srpPasteTip": {
"message": "Maaari mong i-paste ang iyong buong secret recovery phrase sa alinmang patlang",
"description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly."
},
"srpSecurityQuizGetStarted": {
"message": "Magsimula"
},
"srpSecurityQuizIntroduction": {
"message": "Upang ipakita ang iyong Secret Recovery Phrase, kailangan mong sagutin nang tama ang dalawang tanong"
},
"srpSecurityQuizQuestionOneQuestion": {
"message": "Kung mawala mo ang iyong Secret Recovery Phrase, ang MetaMask ay..."
},
"srpSecurityQuizQuestionOneRightAnswer": {
"message": "Hindi ka matutulungan"
},
"srpSecurityQuizQuestionOneRightAnswerDescription": {
"message": "Isulat ito, iukit sa metal, o itago ito sa maraming lihim na lugar upang hindi ito mawala. Kung nawala mo ito, wala na ito ng tuluyan."
},
"srpSecurityQuizQuestionOneRightAnswerTitle": {
"message": "Tama! Walang makakatulong na maibalik ang iyong Secret Recovery Phrase"
},
"srpSecurityQuizQuestionOneWrongAnswer": {
"message": "Maaari itong ibalik para sa iyo"
},
"srpSecurityQuizQuestionOneWrongAnswerDescription": {
"message": "Kung nawala mo ang iyong Secret Recovery Phrase mawawala na ito nang tuluyan. Walang makakatulong sa iyo na maibalik ito, anuman ang maaaring sabihin nila."
},
"srpSecurityQuizQuestionOneWrongAnswerTitle": {
"message": "Mali! Walang makakatulong na maibalik ang iyong Secret Recovery Phrase"
},
"srpSecurityQuizQuestionTwoQuestion": {
"message": "Kung sinuman, kahit isang ahente ng suporta, ay humingi ng iyong Secret Recovery Phrase..."
},
"srpSecurityQuizQuestionTwoRightAnswer": {
"message": "Niloloko ka"
},
"srpSecurityQuizQuestionTwoRightAnswerDescription": {
"message": "Sinumang nagsasabing nangangailangan ng iyong Secret Recovery Phrase ay nagsisinungaling sa iyo. Kung ibabahagi mo ito sa kanila, nanakawin nila ng iyong mga ari-arian."
},
"srpSecurityQuizQuestionTwoRightAnswerTitle": {
"message": "Tama! Ang pagbabahagi ng iyong Secret Recovery Phrase ay hindi kailanman isang magandang ideya"
},
"srpSecurityQuizQuestionTwoWrongAnswer": {
"message": "Dapat mong ibigay sa kanila"
},
"srpSecurityQuizQuestionTwoWrongAnswerDescription": {
"message": "Sinumang nagsasabing nangangailangan ng iyong Secret Recovery Phrase ay nagsisinungaling sa iyo. Kung ibabahagi mo ito sa kanila, nanakawin nila ng iyong mga ari-arian."
},
"srpSecurityQuizQuestionTwoWrongAnswerTitle": {
"message": "Hindi! Huwag kailanman ibahagi ang iyong Secret Recovery Phrase sa sinuman, kailanman"
},
"srpSecurityQuizTitle": {
"message": "Pagsusulit sa seguridad"
},
"srpToggleShow": {
"message": "Ipakita/Itago ang salitang ito ng secret recovery phrase",
"description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase"

View File

@ -911,9 +911,6 @@
"delete": {
"message": "Sil"
},
"deleteAccount": {
"message": "Hesabı Sil"
},
"deleteNetwork": {
"message": "Ağı Sil?"
},
@ -1562,6 +1559,12 @@
"message": "ancak dolandırıcılar talep edilebilir.",
"description": "The text link in 'holdToRevealContent3'"
},
"holdToRevealSRP": {
"message": "GKİ bilgisinin gösterilmesi için tut"
},
"holdToRevealSRPTitle": {
"message": "GKİ bilgini güvende tut"
},
"ignoreAll": {
"message": "Tümünü yoksay"
},
@ -1596,9 +1599,6 @@
"importNFTTokenIdToolTip": {
"message": "Hiçbir iki NFT kimliği birbiriyle aynı olmadığı için her NFT kimliği benzersiz bir tanımlayıcıdır. Yine, OpenSea'de bu sayı 'Detaylar' kısmının altında yer alır. Not alın veya panonuza kopyalayın."
},
"importNFTs": {
"message": "NFS'leri İçe Aktar"
},
"importSelectedTokens": {
"message": "Seçilen tokenleri içe aktar?"
},
@ -1857,9 +1857,6 @@
"lock": {
"message": "Kilitle"
},
"lockTimeTooGreat": {
"message": "Kilitleme süresi çok fazla"
},
"logo": {
"message": "$1 logosu",
"description": "$1 is the name of the ticker"
@ -1953,9 +1950,6 @@
"mismatchedRpcUrl": {
"message": "Kayıtlarımıza göre, sunulan RPC URL adresi değeri bu zincir kimliğinin bilinen bir sağlayıcısı ile uyumlu değil."
},
"missingNFT": {
"message": "NFT'nizi görmüyor musunuz?"
},
"missingSetting": {
"message": "Bir ayarı bulamıyor musun?"
},
@ -2162,9 +2156,6 @@
"noConversionRateAvailable": {
"message": "Dönüşüm oranı mevcut değil"
},
"noNFTs": {
"message": "Henüz NFT yok"
},
"noSnaps": {
"message": "Hiç Snap yüklü değil"
},
@ -3333,12 +3324,63 @@
},
"srpPasteFailedTooManyWords": {
"message": "24'ten fazla sözcük içerdiği için yapıştırma başarısız oldu. Gizli bir kurtarma ifadesi en fazla 24 sözcükten oluşabilir.",
"description": "Description of SRP paste erorr when the pasted content has too many words"
"description": "Description of SRP paste error when the pasted content has too many words"
},
"srpPasteTip": {
"message": "Gizli kurtarma ifadenin tamamını herhangi bir alana yapıştırabilirsin",
"description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly."
},
"srpSecurityQuizGetStarted": {
"message": "Başla"
},
"srpSecurityQuizIntroduction": {
"message": "Gizli Kurtarma İfadenizi görmek için iki soruyu doğru cevaplamanız gerekmektedir"
},
"srpSecurityQuizQuestionOneQuestion": {
"message": "Gizli Kurtarma İfadenizi kaybederseniz MetaMask..."
},
"srpSecurityQuizQuestionOneRightAnswer": {
"message": "Size yardımcı olamaz"
},
"srpSecurityQuizQuestionOneRightAnswerDescription": {
"message": "Bir yere yazın, bir metalin üzerine kazıyın veya asla kaybetmemeniz için birden fazla noktada saklayın. Kaybederseniz sonsuza dek kaybolur."
},
"srpSecurityQuizQuestionOneRightAnswerTitle": {
"message": "Doğru! Hiç kimse Gizli Kurtarma İfadenizi geri almanıza yardımcı olamaz"
},
"srpSecurityQuizQuestionOneWrongAnswer": {
"message": "Size onu tekrar verebilir"
},
"srpSecurityQuizQuestionOneWrongAnswerDescription": {
"message": "Gizli Kurtarma İfadenizi kaybederseniz sonsuza dek kaybolur. Söylediklerinin ne olduğuna bakılmaksızın hiç kimse onu geri almanıza yardımcı olamaz."
},
"srpSecurityQuizQuestionOneWrongAnswerTitle": {
"message": "Yanlış! Hiç kimse Gizli Kurtarma İfadenizi geri almanıza yardımcı olamaz"
},
"srpSecurityQuizQuestionTwoQuestion": {
"message": "Herhangi birisi, bir destek temsilcisi bile sizden Gizli Kurtarma İfadenizi isterse..."
},
"srpSecurityQuizQuestionTwoRightAnswer": {
"message": "Dolandırılıyorsunuzdur"
},
"srpSecurityQuizQuestionTwoRightAnswerDescription": {
"message": "Gizli Kurtarma İfadenizi isteyen kişi size yalan söylüyordur. Kendisi ile paylaşırsanız varlıklarınızı çalacaktır."
},
"srpSecurityQuizQuestionTwoRightAnswerTitle": {
"message": "Doğru! Gizli Kurtarma İfadenizi paylaşmak asla iyi bir fikir değildir"
},
"srpSecurityQuizQuestionTwoWrongAnswer": {
"message": "Kendisine vermelisiniz"
},
"srpSecurityQuizQuestionTwoWrongAnswerDescription": {
"message": "Gizli Kurtarma İfadenizi isteyen kişi size yalan söylüyordur. Kendisi ile paylaşırsanız varlıklarınızı çalacaktır."
},
"srpSecurityQuizQuestionTwoWrongAnswerTitle": {
"message": "Hayır! Gizli Kurtarma İfadenizi asla hiç kimse ile paylaşmayın, asla"
},
"srpSecurityQuizTitle": {
"message": "Güvenlik testi"
},
"srpToggleShow": {
"message": "Gizli kurtarma ifadesinin bu sözcüğünü göster/gizle",
"description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase"

View File

@ -211,9 +211,6 @@
"delete": {
"message": "Видалити"
},
"deleteAccount": {
"message": "Видалити обліковий запис"
},
"deleteNetwork": {
"message": "Видалити мережу?"
},

View File

@ -911,9 +911,6 @@
"delete": {
"message": "Xóa"
},
"deleteAccount": {
"message": "Xóa tài khoản"
},
"deleteNetwork": {
"message": "Xóa mạng?"
},
@ -1562,6 +1559,12 @@
"message": "nhưng những kẻ lừa đảo qua mạng thì có.",
"description": "The text link in 'holdToRevealContent3'"
},
"holdToRevealSRP": {
"message": "Giữ để hiển thị SRP"
},
"holdToRevealSRPTitle": {
"message": "Đảm bảo an toàn cho SRP của bạn"
},
"ignoreAll": {
"message": "Bỏ qua tất cả"
},
@ -1596,9 +1599,6 @@
"importNFTTokenIdToolTip": {
"message": "ID của NFT là một mã nhận dạng duy nhất vì không có hai NFT nào giống hệt nhau. Một lần nữa, trên OpenSea, mã số này nằm bên dưới mục 'Chi tiết'. Hãy ghi chú lại hoặc sao chép vào bộ nhớ đệm."
},
"importNFTs": {
"message": "Nhập NFT"
},
"importSelectedTokens": {
"message": "Nhập các token đã chọn?"
},
@ -1857,9 +1857,6 @@
"lock": {
"message": "Khóa"
},
"lockTimeTooGreat": {
"message": "Thời gian khóa quá lớn"
},
"logo": {
"message": "Logo $1",
"description": "$1 is the name of the ticker"
@ -1953,9 +1950,6 @@
"mismatchedRpcUrl": {
"message": "Theo hồ sơ của chúng tôi, giá trị RPC URL đã gửi không khớp với một nhà cung cấp đã biết cho ID chuỗi này."
},
"missingNFT": {
"message": "Không thấy NFT của mình?"
},
"missingSetting": {
"message": "Không tìm thấy thiết lập?"
},
@ -2162,9 +2156,6 @@
"noConversionRateAvailable": {
"message": "Không có sẵn tỷ lệ quy đổi nào"
},
"noNFTs": {
"message": "Chưa có NFT"
},
"noSnaps": {
"message": "Chưa cài đặt Snap nào"
},
@ -3333,12 +3324,63 @@
},
"srpPasteFailedTooManyWords": {
"message": "Dán không thành công vì cụm từ có nhiều hơn 24 từ. Cụm từ khôi phục bí mật chỉ có tối đa 24 từ.",
"description": "Description of SRP paste erorr when the pasted content has too many words"
"description": "Description of SRP paste error when the pasted content has too many words"
},
"srpPasteTip": {
"message": "Bạn có thể dán toàn bộ cụm từ khôi phục bí mật vào bất kỳ trường nào",
"description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly."
},
"srpSecurityQuizGetStarted": {
"message": "Bắt đầu"
},
"srpSecurityQuizIntroduction": {
"message": "Để hiển thị Cụm từ khôi phục bí mật, bạn cần trả lời đúng hai câu hỏi"
},
"srpSecurityQuizQuestionOneQuestion": {
"message": "Nếu bạn làm mất Cụm từ khôi phục bí mật, MetaMask..."
},
"srpSecurityQuizQuestionOneRightAnswer": {
"message": "Không thể giúp bạn"
},
"srpSecurityQuizQuestionOneRightAnswerDescription": {
"message": "Hãy viết ra, khắc lên kim loại hoặc cất giữ ở nhiều nơi bí mật để bạn không bao giờ làm mất nó. Nếu bạn làm mất, nó sẽ bị mất vĩnh viễn."
},
"srpSecurityQuizQuestionOneRightAnswerTitle": {
"message": "Đúng! Không ai có thể giúp bạn lấy lại Cụm từ khôi phục bí mật"
},
"srpSecurityQuizQuestionOneWrongAnswer": {
"message": "Có thể lấy lại cho bạn"
},
"srpSecurityQuizQuestionOneWrongAnswerDescription": {
"message": "Nếu bạn làm mất Cụm từ khôi phục bí mật, nó sẽ bị mất vĩnh viễn. Dù mọi người có nói gì, thì cũng không ai có thể giúp bạn lấy lại."
},
"srpSecurityQuizQuestionOneWrongAnswerTitle": {
"message": "Sai! Không ai có thể giúp bạn lấy lại Cụm từ khôi phục bí mật"
},
"srpSecurityQuizQuestionTwoQuestion": {
"message": "Nếu có bất kỳ ai, kể cả nhân viên hỗ trợ, hỏi về Cụm từ khôi phục bí mật của bạn..."
},
"srpSecurityQuizQuestionTwoRightAnswer": {
"message": "Bạn đang bị lừa đảo"
},
"srpSecurityQuizQuestionTwoRightAnswerDescription": {
"message": "Bất kỳ ai nói rằng họ cần Cụm từ khôi phục bí mật của bạn thì đều đang nói dối bạn. Nếu bạn chia sẻ với họ thì họ sẽ đánh cắp tài sản của bạn."
},
"srpSecurityQuizQuestionTwoRightAnswerTitle": {
"message": "Chính xác! Chia sẻ Cụm từ khôi phục bí mật chưa bao giờ là một ý hay"
},
"srpSecurityQuizQuestionTwoWrongAnswer": {
"message": "Bạn nên đưa nó cho họ"
},
"srpSecurityQuizQuestionTwoWrongAnswerDescription": {
"message": "Bất kỳ ai nói rằng họ cần Cụm từ khôi phục bí mật của bạn thì đều đang nói dối bạn. Nếu bạn chia sẻ với họ thì họ sẽ đánh cắp tài sản của bạn."
},
"srpSecurityQuizQuestionTwoWrongAnswerTitle": {
"message": "Không! Tuyệt đối không bao giờ chia sẻ Cụm từ khôi phục bí mật của bạn với bất kỳ ai"
},
"srpSecurityQuizTitle": {
"message": "Câu hỏi bảo mật"
},
"srpToggleShow": {
"message": "Hiện/Ẩn từ này của cụm từ khôi phục bí mật",
"description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase"

View File

@ -911,9 +911,6 @@
"delete": {
"message": "删除"
},
"deleteAccount": {
"message": "删除账户"
},
"deleteNetwork": {
"message": "删除网络?"
},
@ -1562,6 +1559,12 @@
"message": "但网络钓鱼者可能会。",
"description": "The text link in 'holdToRevealContent3'"
},
"holdToRevealSRP": {
"message": "按住以显示 助记词"
},
"holdToRevealSRPTitle": {
"message": "保护您的 助记词 安全"
},
"ignoreAll": {
"message": "忽略所有"
},
@ -1596,9 +1599,6 @@
"importNFTTokenIdToolTip": {
"message": "NFT的ID是唯一标识符因为所有NFT都是独一无二的。同样在OpenSea上此数字位于“详情”下方。记下它或将它复制到剪贴板上。"
},
"importNFTs": {
"message": "添加收藏品"
},
"importSelectedTokens": {
"message": "要导入所选代币吗?"
},
@ -1857,9 +1857,6 @@
"lock": {
"message": "注销"
},
"lockTimeTooGreat": {
"message": "锁定时间过长"
},
"logo": {
"message": "$1标志",
"description": "$1 is the name of the ticker"
@ -1953,9 +1950,6 @@
"mismatchedRpcUrl": {
"message": "根据我们的记录所提交的RPC URL值与此链ID的已知提供者不匹配。"
},
"missingNFT": {
"message": "找不到您的 NFT"
},
"missingSetting": {
"message": "找不到设置吗?"
},
@ -2162,9 +2156,6 @@
"noConversionRateAvailable": {
"message": "无可用汇率"
},
"noNFTs": {
"message": "尚无 NFT"
},
"noSnaps": {
"message": "没有安装Snap"
},
@ -3333,12 +3324,63 @@
},
"srpPasteFailedTooManyWords": {
"message": "粘贴失败因为它包含超过24个单词。一个助记词最多可包含24个单词。",
"description": "Description of SRP paste erorr when the pasted content has too many words"
"description": "Description of SRP paste error when the pasted content has too many words"
},
"srpPasteTip": {
"message": "您可以将整个助记词粘贴到任何字段中",
"description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly."
},
"srpSecurityQuizGetStarted": {
"message": "开始"
},
"srpSecurityQuizIntroduction": {
"message": "要查看助记词,您需要答对两个问题"
},
"srpSecurityQuizQuestionOneQuestion": {
"message": "如果您丢失了助记词MetaMask......"
},
"srpSecurityQuizQuestionOneRightAnswer": {
"message": "无法帮助您"
},
"srpSecurityQuizQuestionOneRightAnswerDescription": {
"message": "将它写下来、刻在金属上,或保存在多个秘密位置,这样您就不会丢失它。如果丢失了,它就会永远消失。"
},
"srpSecurityQuizQuestionOneRightAnswerTitle": {
"message": "答对了!没有人能够帮您找回您的助记词"
},
"srpSecurityQuizQuestionOneWrongAnswer": {
"message": "可以为您找回来"
},
"srpSecurityQuizQuestionOneWrongAnswerDescription": {
"message": "一旦遗失助记词,它将永远消失。无论他人如何保证,无人能够帮您找回。"
},
"srpSecurityQuizQuestionOneWrongAnswerTitle": {
"message": "答错了!没有人能够帮您找回您的助记词"
},
"srpSecurityQuizQuestionTwoQuestion": {
"message": "如果有人(即使是技术支持人员)查问您的助记词......"
},
"srpSecurityQuizQuestionTwoRightAnswer": {
"message": "就是在对您进行诈骗"
},
"srpSecurityQuizQuestionTwoRightAnswerDescription": {
"message": "任何声称需要您的助记词的人都在对您进行欺诈。如果您与他们分享助记词,他们就会偷窃您的资产。"
},
"srpSecurityQuizQuestionTwoRightAnswerTitle": {
"message": "答对了!分享您的助记词绝对不是个好主意"
},
"srpSecurityQuizQuestionTwoWrongAnswer": {
"message": "您应该交给他们"
},
"srpSecurityQuizQuestionTwoWrongAnswerDescription": {
"message": "任何声称需要您的助记词的人都在对您进行欺诈。如果您与他们分享助记词,他们就会偷窃您的资产。"
},
"srpSecurityQuizQuestionTwoWrongAnswerTitle": {
"message": "不!永远不要与任何人分享您的助记词"
},
"srpSecurityQuizTitle": {
"message": "安全问答"
},
"srpToggleShow": {
"message": "显示/隐藏助记词中的这个单词",
"description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase"

View File

@ -408,9 +408,6 @@
"delete": {
"message": "刪除"
},
"deleteAccount": {
"message": "刪除帳戶"
},
"deleteNetwork": {
"message": "刪除網路?"
},
@ -821,9 +818,6 @@
"lock": {
"message": "鎖定"
},
"lockTimeTooGreat": {
"message": "鎖定時間過長"
},
"mainnet": {
"message": "以太坊 主網路"
},

View File

@ -1,14 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_735_24127)">
<path
d="M3.99902 19C7.24796 17.5 8.87242 16.75 10.4969 16.75M16.9948 19C13.7458 17.5 12.1214 16.75 10.4969 16.75M10.4969 16.75V11.5M10.4969 11.5L10 10.4091M10.4969 11.5V9.5L10.9967 8.5M10 10.4091C10 10.4091 5.00889 11.0985 2.99935 9.5C1.29118 8.14126 1 4.5 1 4.5C1 4.5 5.55008 3.95155 7.54545 5.90909C8.91802 7.25563 10 10.4091 10 10.4091ZM10.9967 8.5C10.9967 8.5 11.5374 4.11404 13.4959 2.5C15.2137 1.08439 18.9941 1 18.9941 1C18.9941 1 19.1777 5.2683 17.4946 7C15.6792 8.86783 10.9967 8.5 10.9967 8.5Z"
strokeWidth="1.5"
strokeLinecap="round"
/>
</g>
<defs>
<clipPath id="clip0_735_24127">
<rect width="20" height="20" fill="white" />
</clipPath>
</defs>
</svg>
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.59902 17.1C6.52306 15.75 7.98508 15.075 9.44711 15.075M15.2952 17.1C12.3711 15.75 10.9091 15.075 9.44711 15.075M9.44711 15.075V10.35M9.44711 10.35L8.9999 9.36821M9.44711 10.35V8.55002L9.89696 7.65002M8.9999 9.36821C8.9999 9.36821 4.5079 9.98865 2.69931 8.55002C1.16197 7.32715 0.899902 4.05002 0.899902 4.05002C0.899902 4.05002 4.99497 3.55642 6.79081 5.31821C8.02612 6.53009 8.9999 9.36821 8.9999 9.36821ZM9.89696 7.65002C9.89696 7.65002 10.3835 3.70266 12.1462 2.25002C13.6922 0.975978 17.0946 0.900024 17.0946 0.900024C17.0946 0.900024 17.2598 4.74149 15.745 6.30002C14.1112 7.98107 9.89696 7.65002 9.89696 7.65002Z" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 873 B

After

Width:  |  Height:  |  Size: 794 B

View File

@ -0,0 +1,218 @@
<svg width="328" height="227" viewBox="0 0 328 227" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.9141 117.201L47.3534 120.85V95.3771L21.9141 91.7284V117.201Z" fill="#43AEFC"/>
<path d="M64.3018 111.091L47.3535 120.85V95.3767L64.3018 85.6187V111.091Z" fill="#037DD6"/>
<path d="M38.8623 81.9697L21.9141 91.7278L47.3534 95.3765L64.3017 85.6184L38.8623 81.9697Z" fill="#75C4FD"/>
<path d="M40.7607 114.435L28.5071 112.517C27.3207 112.331 26.3716 111.228 26.3716 110.04V100.825L40.0827 102.963C41.642 103.201 42.9131 104.677 42.9131 106.238V112.619C42.9131 113.807 41.947 114.622 40.7607 114.435Z" fill="white"/>
<path d="M31.8627 99.5862L27.9137 98.9753C27.0494 98.8395 26.3545 99.4335 26.3545 100.282V102.726L34.6253 104.016L33.2185 100.689C32.9982 100.112 32.4559 99.688 31.8627 99.5862Z" fill="white"/>
<path d="M289.51 71.3134L264.342 74.9281V49.7268L289.51 46.1121V71.3134Z" fill="#037DD6"/>
<path d="M247.563 65.2527L264.342 74.926V49.7247L247.563 40.0515V65.2527Z" fill="#43AEFC"/>
<path d="M272.732 36.438L289.51 46.1112L264.342 49.726L247.563 40.0527L272.732 36.438Z" fill="#75C4FD"/>
<path d="M271.749 140.044C271.494 141.079 271.24 142.114 270.969 143.133C270.257 145.78 269.444 148.393 268.545 150.99C268.359 151.516 268.172 152.042 267.986 152.551C265.63 159.017 262.681 165.245 259.19 171.151C258.834 171.745 258.478 172.356 258.105 172.933C255.8 176.683 253.275 180.281 250.546 183.743C249.597 184.948 248.631 186.136 247.631 187.29C246.631 188.444 245.614 189.581 244.58 190.701C241.36 194.146 237.92 197.405 234.259 200.442C233.479 201.087 232.7 201.715 231.903 202.343C221.124 210.811 208.616 217.43 194.702 221.571C180.787 225.711 166.686 227.035 153.043 225.83C152.043 225.745 151.043 225.644 150.043 225.525C148.145 225.304 146.247 225.05 144.382 224.727C140.654 224.099 136.959 223.285 133.315 222.3C132.773 222.148 132.213 221.995 131.671 221.842C130.197 221.418 128.739 220.96 127.298 220.468C123.129 219.076 119.045 217.43 115.079 215.563C114.621 215.342 114.146 215.122 113.689 214.884C113.519 214.799 113.367 214.732 113.197 214.647C111.079 213.594 108.977 212.474 106.927 211.286C104.503 209.895 102.147 208.418 99.8421 206.857C99.3337 206.518 98.8083 206.161 98.2998 205.788C97.5372 205.262 96.7915 204.719 96.0457 204.159C94.7407 203.191 93.4696 202.207 92.2154 201.189C90.7918 200.035 89.402 198.847 88.0292 197.625C88.0292 197.625 88.0122 197.625 88.0122 197.608C87.2157 196.895 86.4361 196.183 85.6564 195.436C79.3008 189.411 73.6062 182.606 68.759 175.105C68.5217 174.749 68.2845 174.375 68.0641 174.019C65.9456 170.659 63.9965 167.18 62.217 163.565C61.8272 162.751 61.4373 161.953 61.0475 161.121C60.234 159.373 59.4713 157.592 58.7595 155.793C58.4545 155.012 58.1494 154.231 57.8443 153.434C57.7596 153.179 57.6579 152.942 57.5731 152.687C57.4545 152.365 57.3359 152.042 57.2172 151.72C56.5562 149.853 55.9292 147.952 55.3529 146.018C54.2343 142.25 53.3191 138.449 52.6073 134.664C52.5056 134.155 52.4209 133.663 52.3361 133.154C52.2853 132.882 52.2514 132.628 52.2005 132.356C51.7938 129.946 51.4718 127.52 51.2345 125.11C51.1667 124.397 51.0989 123.684 51.0481 122.972C50.8277 120.273 50.7091 117.575 50.6921 114.894V114.758C50.6921 113.96 50.6921 113.146 50.7091 112.348C50.726 111.313 50.7599 110.278 50.7938 109.242C50.8108 108.988 50.8108 108.75 50.8277 108.496C51.1836 101.08 52.2853 93.7652 54.0818 86.6715C54.1496 86.4169 54.2174 86.1624 54.2682 85.9078C54.7428 84.075 55.2682 82.2761 55.8275 80.4772C56.2342 79.1875 56.6579 77.9147 57.0986 76.6419C57.4037 75.7764 57.7257 74.9109 58.0477 74.0454C58.0477 74.0454 58.0477 74.0454 58.0477 74.0284C58.7087 72.3144 59.3866 70.6173 60.1323 68.9542C65.4032 56.9221 72.7588 45.8572 81.8939 36.3028L81.9278 36.2689C84.0463 34.0627 86.2496 31.9244 88.5546 29.9049C88.6732 29.8031 88.7919 29.7012 88.9105 29.5825C100.673 19.2474 114.791 11.2373 130.773 6.48549C146.704 1.73373 162.873 0.698523 178.313 2.88773C181.567 3.34593 184.787 3.9399 187.956 4.68661C197.329 6.85884 206.345 10.236 214.802 14.6823C215.718 15.1575 216.616 15.6496 217.531 16.1587C219.734 17.3976 221.904 18.7043 224.022 20.0959C224.971 20.7068 225.903 21.3517 226.836 21.9966C227.412 22.3869 227.971 22.7942 228.53 23.2015C229.276 23.7446 230.022 24.2876 230.751 24.8477C231.683 25.5604 232.598 26.2732 233.496 27.0199C233.632 27.1387 233.767 27.2405 233.903 27.3593C233.954 27.4102 234.022 27.4611 234.072 27.4951C235.513 28.683 236.92 29.9049 238.31 31.1777C241.377 33.9778 244.309 36.9816 247.072 40.1382C247.919 41.1055 248.733 42.0898 249.546 43.0741C249.716 43.2947 249.902 43.4983 250.072 43.7189C252.004 46.1288 253.851 48.6404 255.597 51.2369C256.071 51.9327 256.529 52.6455 256.987 53.3582C258.427 55.6153 259.8 57.9403 261.088 60.3162C261.427 60.9441 261.766 61.572 262.088 62.1999C262.291 62.6072 262.512 63.0145 262.715 63.4218C262.851 63.6764 262.969 63.9309 263.088 64.1855C265.867 69.8027 268.189 75.7424 270.037 81.9537C270.071 82.0894 270.121 82.2252 270.155 82.361L270.443 83.3622C271.02 85.4157 271.545 87.4691 272.003 89.5225C272.189 90.3371 272.359 91.1687 272.528 92.0002C272.63 92.5433 272.748 93.0864 272.833 93.6124C273.087 94.9701 273.308 96.3447 273.511 97.7024C273.647 98.6018 273.765 99.4843 273.867 100.384C274.155 102.743 274.375 105.102 274.511 107.46C274.528 107.851 274.562 108.258 274.579 108.648C275.172 119.425 274.155 129.946 271.749 140.044Z" fill="url(#paint0_linear_6966_12826)"/>
<path d="M146.992 58.4162C149.873 56.3288 155.06 53.8341 154.246 49.4897C153.483 45.4677 148.348 45.1792 145.111 45.3319C139.332 45.6034 134.095 48.3017 128.519 49.4897C123.621 50.5249 118.299 49.9139 114.774 46.0446C112.469 43.516 111.248 40.2407 109.028 37.6442C106.469 34.6574 102.672 33.5204 98.8252 33.8258C96.7405 33.9955 94.6559 34.5216 92.5374 34.3349C90.2324 34.1313 88.2834 32.4003 88.6054 29.9735C86.3004 32.01 84.0971 34.1313 81.9786 36.3375L81.9447 36.3714C72.8096 45.9089 65.454 56.9907 60.1831 69.0228C59.4543 70.7029 58.7595 72.4 58.0985 74.097C58.0985 74.097 58.0985 74.097 58.0985 74.114C57.7765 74.9795 57.4544 75.845 57.1494 76.7105C56.7087 77.9833 56.2681 79.2561 55.8783 80.5458C52.675 90.7621 50.9124 101.488 50.7599 112.417C50.7429 113.214 50.7429 114.012 50.7429 114.826C52.4547 115.879 53.9292 117.372 55.3359 118.798C57.0985 120.596 58.8273 122.48 60.861 123.957C62.2678 124.975 63.9456 125.637 65.6405 124.924C67.8946 123.974 69.3013 121.666 70.7927 119.85C72.3859 117.915 74.4027 116.286 76.8602 115.641C79.216 115.013 81.7413 115.098 84.0463 114.3C86.1309 113.571 87.4698 111.891 88.1478 109.854C88.8935 107.631 88.9274 105.221 89.6901 103.015C90.5036 100.639 92.6221 98.2801 94.961 97.3128C99.198 95.5648 102.808 99.9432 104.977 102.981C107.621 106.681 109.553 111.364 113.401 114.012C115.502 115.454 118.045 115.828 120.468 114.996C122.57 114.266 124.553 112.977 125.536 110.923C127.519 106.816 125.62 102.082 126.434 97.771C127.23 93.5792 130.078 90.7282 133.484 88.4372C137.094 85.9934 140.976 83.923 144.145 80.8853C146.094 79.0355 147.907 76.7275 148.263 73.9782C148.585 71.5345 147.535 69.2604 145.941 67.4785C145.009 66.4263 143.06 65.2553 143.111 63.6771C143.162 62.4722 144.077 61.3012 144.806 60.4187C145.416 59.6381 146.179 59.0102 146.992 58.4162Z" fill="url(#paint1_linear_6966_12826)"/>
<path d="M101.266 159.459C96.4185 155.505 89.6053 155.963 84.4361 152.688C81.5718 150.872 79.6397 148.055 77.4873 145.526C75.1993 142.828 72.5214 140.689 69.047 139.773C65.4032 138.823 61.5729 139.094 57.9629 137.957C55.8952 137.295 54.1495 136.175 52.6411 134.733C53.3529 138.517 54.2681 142.302 55.3867 146.086C56.1325 148.598 56.9629 151.075 57.8781 153.502C58.1663 154.3 58.4713 155.08 58.7933 155.861C59.5052 157.66 60.2678 159.442 61.0814 161.19C61.4542 162.004 61.844 162.819 62.2508 163.634C68.2166 175.802 76.1992 186.527 85.6733 195.504C86.4529 196.234 87.2326 196.964 88.0291 197.677C88.0291 197.677 88.0291 197.677 88.0461 197.694C89.4189 198.915 90.8086 200.103 92.2323 201.257C92.4018 200.613 92.5374 199.951 92.656 199.306C93.317 195.776 94.1983 192.331 95.4694 188.971C96.8422 185.322 98.5879 181.826 100.418 178.381C102.113 175.174 104.249 171.915 104.791 168.25C105.282 164.822 103.91 161.614 101.266 159.459Z" fill="url(#paint2_linear_6966_12826)"/>
<path d="M239.411 190.173C237.886 190.071 236.293 189.935 234.768 190.088C232.547 190.291 230.514 191.344 228.7 192.582C226.683 193.957 224.87 195.62 222.836 196.978C221.734 197.708 220.548 198.386 219.277 198.726C217.887 199.082 216.446 198.997 215.04 198.845C212.582 198.556 210.074 198.149 207.82 199.455C206.006 200.508 204.803 202.324 203.735 204.071C202.6 205.938 201.532 207.992 199.82 209.417C197.719 211.165 195.261 211.471 192.702 210.809C191.549 210.503 190.414 210.181 189.228 210.011C187.77 209.808 186.279 209.841 184.821 209.892C183.245 209.943 181.669 210.045 180.092 210.062C178.635 210.096 177.16 209.926 175.703 210.011C173.788 210.113 172.161 210.775 170.703 212.031C168.89 213.592 167.313 215.374 165.263 216.664C161.263 219.158 156.263 219.226 151.687 219.498C148.84 219.667 144.501 220.126 144.332 223.791C144.315 224.114 144.332 224.436 144.382 224.742C146.264 225.064 148.145 225.319 150.043 225.539C151.043 225.658 152.043 225.76 153.043 225.845C166.686 227.033 180.77 225.726 194.702 221.585C208.616 217.444 221.141 210.826 231.903 202.357C232.7 201.73 233.479 201.102 234.259 200.457C237.92 197.419 241.36 194.161 244.581 190.716C242.852 190.478 241.14 190.291 239.411 190.173Z" fill="url(#paint3_linear_6966_12826)"/>
<path d="M252.953 86.96C251.479 82.7513 247.631 79.8154 243.733 77.762C241.428 76.5571 239.005 75.6067 236.53 74.7921C233.7 73.8588 230.853 73.0781 228.259 71.5677C217.243 65.085 218.429 49.506 197.583 49.506C188.6 49.506 185.431 53.2056 181.703 58.0761C178.109 62.777 177.025 68.8185 172.839 73.1799C169.381 76.7947 164.873 79.2554 161.415 82.8532C157.483 86.9261 155.721 92.2379 155.856 97.8042C155.992 103.71 158.331 109.497 162.042 114.215C163.89 116.557 166.076 118.678 168.534 120.426C170.771 122.038 173.178 123.464 175.245 125.297C177.211 127.045 178.55 129.234 179.635 131.576C181.025 134.596 182.957 137.091 185.262 139.535C187.702 142.114 190.261 144.677 191.804 147.884C193.126 150.617 193.685 153.57 193.837 156.556C194.159 162.309 193.38 168.147 195.414 173.731C196.956 177.973 201.481 183.217 206.583 182.946C212.565 182.64 213.514 174.986 214.65 170.676C215.023 169.25 215.447 167.808 216.124 166.484C216.921 164.923 218.023 163.48 219.023 162.038C220.887 159.34 222.802 156.692 224.616 153.96C228.192 148.546 230.954 142.793 232.022 136.378C232.615 132.832 232.785 129.302 233.852 125.84C234.801 122.768 236.208 119.866 237.903 117.117C241.344 111.516 245.801 106.578 249.563 101.181C252.428 97.0745 254.716 91.9833 252.953 86.96Z" fill="url(#paint4_linear_6966_12826)"/>
<path d="M233.954 27.4937C232.92 26.6451 231.869 25.7966 230.801 24.982C230.073 24.422 229.327 23.8789 228.581 23.3359C228.022 22.9286 227.446 22.5213 226.886 22.131C225.954 21.4861 225.022 20.8412 224.073 20.2303C221.954 18.8387 219.785 17.5319 217.582 16.2931C217.056 16.8361 216.514 17.3453 215.887 17.7865C213.09 19.7551 209.633 19.1102 206.447 18.8387C203.532 18.5841 200.549 18.7029 198.278 20.7733C196.43 22.4534 195.447 24.8463 194.261 26.9846C193.634 28.1555 192.939 29.2926 192.024 30.2599C191.142 31.1933 190.075 31.8042 188.973 32.4321C187.041 33.5182 184.888 34.7401 183.702 36.6917C182.465 38.7112 183.024 41.2398 184.583 42.9369C186.177 44.6679 188.804 44.9225 191.007 44.9734C193.532 45.0243 196.125 44.583 198.634 44.9903C201.362 45.4316 203.769 47.0268 206.498 47.4002C207.921 47.5868 209.345 47.3832 210.684 46.8741C212.328 46.2462 213.87 45.2449 215.633 44.9564C217.378 44.6849 218.497 45.4485 219.7 46.5856C220.565 47.4002 221.48 48.2147 222.683 48.4354C227.124 49.2839 228.632 44.3794 229.53 41.0192C230.005 39.2713 230.327 37.2178 231.395 35.7244C232.615 34.0273 234.682 33.1279 236.513 32.2624C237.106 31.9909 237.767 31.7024 238.394 31.346C236.937 30.0053 235.462 28.7325 233.954 27.4937Z" fill="url(#paint5_linear_6966_12826)"/>
<path d="M272.613 92.1372C272.444 91.3057 272.274 90.4911 272.088 89.6595C271.528 87.1309 270.867 84.6023 270.122 82.0906C268.054 75.1497 265.376 68.5481 262.173 62.3369C261.851 61.709 261.512 61.0641 261.173 60.4532C259.885 58.0773 258.512 55.7523 257.071 53.4952C256.614 52.7825 256.156 52.0697 255.682 51.3739C253.055 47.4707 250.207 43.7711 247.157 40.2751C247.089 41.3613 246.682 42.4134 246.123 43.4656C244.598 46.3506 242.326 48.5228 240.157 50.8987C238.208 53.0031 236.242 55.4468 235.886 58.3827C235.734 59.6895 235.869 61.1659 236.581 62.3199C237.225 63.3891 238.293 64.017 239.377 64.5431C240.53 65.0861 241.716 65.5783 242.598 66.5456C243.648 67.6996 244.174 69.1591 244.92 70.5167C246.53 73.4356 249.733 74.4539 252.445 76.0491C253.97 76.9486 255.411 78.0856 256.122 79.7657C256.885 81.5985 256.766 83.635 256.936 85.5696C257.173 88.2001 257.987 91.1869 260.19 92.85C262.19 94.3604 264.834 94.3095 267.207 94.1228C269.105 93.97 271.037 93.6815 272.952 93.7664C272.816 93.2233 272.715 92.6803 272.613 92.1372Z" fill="url(#paint6_linear_6966_12826)"/>
<path opacity="0.4" d="M101.113 207.74C49.4549 173.647 35.1844 104.084 69.2166 52.3578C99.1811 6.8427 156.602 -9.68664 205.142 10.2538C208.074 11.4587 210.972 12.7994 213.836 14.2588C217.429 16.1086 220.955 18.1621 224.395 20.4361C132.417 24.0339 55.2342 83.2952 101.113 207.74Z" fill="url(#paint7_linear_6966_12826)"/>
<path d="M173.33 224.912C163.466 224.912 153.416 223.113 143.484 219.481C130.858 214.865 119.113 207.602 108.588 197.894C98.4188 188.527 89.8091 177.275 82.9959 164.462C76.1827 151.65 71.488 137.92 69.0644 123.665C66.556 108.901 66.6069 94.1872 69.2 79.9659C71.9117 65.0657 77.2504 51.642 85.0466 40.085C92.5886 28.9184 102.029 20.0088 113.147 13.6449C133.807 1.78242 158.28 0.0174789 182.025 8.68945C193.651 12.9321 204.566 19.4658 214.464 28.0698C225.666 37.811 235.073 49.7583 242.445 63.5893C249.292 76.419 253.987 90.1652 256.428 104.454C258.953 119.253 258.902 133.966 256.309 148.222L255.936 148.154L256.309 148.222C253.597 163.122 248.259 176.545 240.462 188.102C232.92 199.269 223.48 208.179 212.362 214.543C200.346 221.433 187.024 224.912 173.33 224.912ZM152.179 4.00557C138.603 4.00557 125.417 7.46756 113.503 14.3067C102.503 20.6198 93.1309 29.4445 85.6567 40.5093C77.9283 51.9814 72.6235 65.3033 69.9288 80.1016C67.3526 94.2381 67.3018 108.85 69.7932 123.529C72.1998 137.717 76.8606 151.361 83.6399 164.106C90.4192 176.851 98.9781 188.018 109.079 197.334C119.536 206.974 131.197 214.186 143.722 218.768C153.568 222.366 163.534 224.148 173.313 224.148C186.889 224.148 200.075 220.703 211.989 213.864C222.989 207.551 232.361 198.726 239.835 187.661C247.564 176.189 252.868 162.867 255.563 148.069C258.139 133.915 258.19 119.27 255.682 104.556C253.258 90.3519 248.581 76.6736 241.784 63.9287C234.446 50.1825 225.09 38.3201 213.972 28.6299C204.142 20.0767 193.312 13.6109 181.77 9.38524C171.923 5.80445 161.941 4.00557 152.179 4.00557Z" fill="white"/>
<path d="M183.075 223.334C182.686 223.334 182.313 223.334 181.923 223.317C172.991 223.113 163.788 220.584 154.535 215.782C144.196 210.402 134.366 202.511 125.35 192.295C116.638 182.435 109.045 170.861 102.758 157.878C96.4698 144.896 91.809 131.166 88.9278 117.081C85.9279 102.486 84.9619 88.1291 86.0466 74.3999C88.4193 44.4129 100.571 21.2141 119.401 10.7602C126.807 6.65332 134.942 4.65079 143.569 4.85444C152.501 5.05809 161.704 7.5867 170.957 12.3894C180.482 17.3448 189.601 24.4555 198.041 33.5517C207.583 43.8359 215.887 56.1565 222.684 70.1572C228.988 83.1567 233.666 96.9029 236.564 111.022C239.581 125.651 240.547 140.025 239.462 153.771C237.09 183.758 224.938 206.957 206.108 217.411C199.024 221.348 191.295 223.334 183.075 223.334ZM142.417 5.60115C134.332 5.60115 126.722 7.55276 119.757 11.4221C101.164 21.7571 89.1481 44.7353 86.7923 74.4678C85.7076 88.1121 86.6737 102.401 89.6566 116.928C92.5378 130.963 97.1646 144.624 103.435 157.556C109.689 170.47 117.265 181.993 125.926 191.802C134.891 201.934 144.637 209.791 154.89 215.12C164.026 219.872 173.127 222.383 181.94 222.57C190.431 222.757 198.448 220.805 205.735 216.766C224.328 206.431 236.344 183.47 238.7 153.737C239.784 140.076 238.818 125.753 235.818 111.209C232.92 97.1574 228.277 83.4622 221.989 70.5306C215.226 56.5977 206.973 44.345 197.481 34.1117C189.092 25.0834 180.059 18.0236 170.602 13.1021C161.466 8.31644 152.365 5.82176 143.552 5.61812C143.179 5.60115 142.789 5.60115 142.417 5.60115Z" fill="white"/>
<path d="M191.329 222.008C189.431 222.008 187.465 221.77 185.465 221.312C179.635 219.971 173.432 216.679 167.059 211.554C151.721 199.233 136.84 177.511 125.146 150.426C113.452 123.341 106.351 94.1176 105.13 68.1186C104.401 52.5736 105.808 39.3874 109.283 28.9336C112.621 18.9039 117.875 11.8442 124.468 8.484C129.112 6.10812 134.349 5.58203 140.027 6.88877C145.857 8.22944 152.06 11.5217 158.433 16.6469C165.585 22.3829 172.652 30.1894 179.448 39.8117C187.143 50.7068 194.159 63.4347 200.295 77.6391C212.023 104.758 219.141 134.049 220.361 160.082C221.09 175.627 219.684 188.813 216.209 199.267C212.87 209.297 207.616 216.374 201.024 219.717C198.024 221.227 194.787 222.008 191.329 222.008ZM134.163 6.92271C130.824 6.92271 127.705 7.66942 124.807 9.12889C118.401 12.4042 113.265 19.3282 109.994 29.1542C106.537 39.5232 105.147 52.6075 105.876 68.0507C107.096 93.9648 114.181 123.103 125.824 150.104C137.468 177.087 152.28 198.69 167.517 210.943C173.805 216 179.889 219.225 185.618 220.548C191.109 221.821 196.176 221.295 200.668 219.021C207.074 215.746 212.209 208.839 215.48 198.996C218.938 188.627 220.328 175.542 219.599 160.099C218.379 134.151 211.277 104.962 199.583 77.9107C193.465 63.7402 186.465 51.0632 178.804 40.219C172.042 30.6476 165.025 22.909 157.941 17.2069C151.653 12.1496 145.569 8.92524 139.84 7.60153C137.908 7.1603 136.01 6.92271 134.163 6.92271Z" fill="white"/>
<path d="M196.075 221.248C191.38 221.248 185.381 216.632 178.5 207.671C156.535 179.059 131.451 113.875 122.587 62.3521C119.943 46.9428 118.943 33.7906 119.706 24.3041C120.452 15.0042 122.909 9.18331 126.79 7.48625C131.773 5.29705 138.739 9.81122 146.976 20.5366C151.942 27.0024 157.128 35.4029 162.433 45.5173C168.415 56.9555 174.263 70.0737 179.805 84.4648C190.38 111.94 198.583 140.841 202.888 165.856C205.532 181.265 206.532 194.417 205.769 203.904C205.024 213.204 202.566 219.024 198.685 220.722C197.871 221.061 197.007 221.248 196.075 221.248ZM129.434 7.6899C128.536 7.6899 127.773 7.87658 127.112 8.16508C123.502 9.76031 121.197 15.3436 120.469 24.355C119.706 33.7737 120.706 46.875 123.333 62.2164C132.18 113.637 157.196 178.668 179.093 207.196C187.126 217.667 192.55 220.484 196.041 220.484C196.939 220.484 197.702 220.297 198.363 220.009C201.973 218.414 204.278 212.83 205.007 203.819C205.769 194.4 204.769 181.299 202.142 165.958C197.854 141.011 189.668 112.161 179.11 84.7194C173.568 70.3453 167.737 57.2779 161.772 45.8567C156.501 35.7762 151.315 27.4097 146.383 20.9779C138.349 10.507 132.943 7.6899 129.434 7.6899Z" fill="white"/>
<path d="M127.816 7.51205L127.093 7.75269L197.693 220.684L198.417 220.444L127.816 7.51205Z" fill="white"/>
<path d="M263.156 64.3212C261.156 66.8668 256.275 70.2269 248.529 74.3338C217.972 90.5916 151.992 113.824 101.452 126.145C90.6054 128.792 69.0133 133.663 57.302 133.663C55.353 133.663 53.6751 133.527 52.3531 133.222C52.3023 132.95 52.2684 132.696 52.2175 132.424C57.3529 133.799 72.0979 132.509 101.266 125.398C151.755 113.095 217.65 89.8789 248.157 73.655C257.783 68.5299 261.478 65.3903 262.783 63.5405C262.902 63.829 263.037 64.0836 263.156 64.3212Z" fill="white"/>
<path d="M250.123 43.8545C248.716 46.1964 243.869 49.5057 235.666 53.7483C206.786 68.6315 144.687 90.3878 97.2491 102.233C77.5382 107.155 62.878 109.734 55.0309 109.734C53.2852 109.734 51.8785 109.615 50.8108 109.344C50.8277 109.089 50.8277 108.852 50.8447 108.597C56.1326 110.074 72.9961 107.511 97.0626 101.504C144.45 89.675 206.481 67.9527 235.31 53.0865C246.072 47.5371 248.953 44.6012 249.614 43.2266C249.784 43.4302 249.953 43.6339 250.123 43.8545Z" fill="white"/>
<path d="M234.14 27.6301C233.344 28.4956 230.581 30.3963 220.243 34.7917C193.465 46.1789 137.179 65.2539 94.7747 77.3199C82.233 80.8838 71.7251 83.5991 64.4034 85.1603C58.7088 86.3822 55.709 86.7386 54.0989 86.7556C54.1667 86.501 54.2345 86.2465 54.2853 85.9919C55.7768 85.941 58.6071 85.6185 63.9628 84.4815C71.2844 82.9372 81.8602 80.2049 94.5375 76.6072C136.908 64.5581 193.16 45.5001 219.921 34.1128C230.276 29.7175 232.903 27.8677 233.547 27.1549C233.683 27.2737 233.818 27.3755 233.954 27.4943C234.022 27.5452 234.073 27.5961 234.14 27.6301Z" fill="white"/>
<path d="M65.7594 59.6876C65.1831 59.6876 65.0814 59.5518 64.9797 59.45C64.8442 59.2973 64.8272 59.0766 64.9289 58.89L65.6068 59.2294C65.6238 59.1954 65.6407 59.1275 65.6068 59.0427C65.5729 58.9578 65.5221 58.9239 65.5051 58.9069C66.3864 59.2633 75.4199 57.2099 97.995 50.3028C131.129 40.1714 175.381 25.0846 196.651 16.6841C206.447 12.8148 207.633 11.8305 207.752 11.6269C207.735 11.6439 207.718 11.7118 207.752 11.8136C207.769 11.8815 207.82 11.9154 207.837 11.9493L208.006 11.2196C208.413 11.3045 208.481 11.593 208.481 11.6948C208.515 12.136 208.566 12.7979 196.922 17.3969C175.635 25.7973 131.349 40.8842 98.1984 51.0326C88.2158 54.0873 79.9281 56.4801 74.2335 57.9566C68.8778 59.3312 66.6915 59.6876 65.7594 59.6876Z" fill="white"/>
<path d="M84.4874 34.7934C84.0298 34.7934 83.962 34.6746 83.8773 34.5388C83.8264 34.471 83.7078 34.1994 84.0129 33.9109L84.5383 34.454C84.5552 34.42 84.5891 34.3182 84.5552 34.2164C84.5213 34.0976 84.4196 34.0467 84.3857 34.0297C85.0467 34.1994 91.0125 32.7909 105.639 28.2937C127.4 21.5903 156.484 11.6964 170.466 6.2319C176.771 3.77116 177.618 3.14325 177.737 3.00748C177.72 3.02445 177.686 3.09234 177.703 3.19416C177.72 3.27901 177.771 3.36387 177.839 3.41478L178.262 2.78687C178.398 2.88869 178.466 3.04142 178.449 3.21113C178.415 3.55054 178.364 3.97481 170.754 6.94466C156.738 12.4092 127.638 22.32 105.859 29.0234C99.2494 31.0599 93.792 32.6551 90.0464 33.6394C86.6059 34.5388 85.1484 34.7934 84.4874 34.7934Z" fill="white"/>
<path d="M270.528 83.5136C268.461 86.7041 263.919 90.5394 256.953 94.9518C226.853 114.112 160.161 138.447 108.3 149.173C92.6393 152.414 79.3858 154.179 70.0134 154.264C69.7592 154.264 69.5049 154.264 69.2507 154.264C64.0984 154.264 60.2003 153.755 57.5903 152.754C57.4716 152.431 57.353 152.109 57.2344 151.786C59.4377 152.804 63.3188 153.568 69.9964 153.517C79.318 153.432 92.5207 151.684 108.13 148.443C159.924 137.735 226.497 113.45 256.529 94.3238C265.02 88.9272 268.766 85.1088 270.223 82.5123L270.528 83.5136Z" fill="white"/>
<path d="M274.664 108.784C272.376 111.669 268.291 115.03 262.41 118.814C233.158 137.702 168.229 161.444 117.672 171.762C101.995 174.953 89.2836 176.565 80.1993 176.565C75.2843 176.565 71.454 176.09 68.7762 175.156C68.5389 174.8 68.3016 174.427 68.0813 174.07C70.3354 175.088 74.2505 175.852 80.962 175.784C90.0124 175.699 102.656 174.053 117.536 171.015C168.025 160.731 232.836 137.023 262.02 118.186C269.156 113.57 272.85 110.125 274.63 107.596C274.63 108.004 274.647 108.394 274.664 108.784Z" fill="white"/>
<path d="M94.9102 193.724C86.7581 193.724 81.4532 192.417 79.5381 189.769L80.1482 189.328C81.9108 191.755 87.0123 192.96 94.8085 192.96C103.147 192.96 114.536 191.585 128.197 188.836C174.94 179.451 234.75 156.982 261.546 138.739C272.986 130.949 274.782 126.486 274.274 124.11L275.02 123.957C275.833 127.742 271.443 132.918 261.969 139.367C235.106 157.661 175.16 180.181 128.349 189.583C114.621 192.349 103.266 193.724 94.9102 193.724Z" fill="white"/>
<path d="M268.546 150.991C268.359 151.517 268.173 152.043 267.986 152.552C266.139 154.164 263.8 155.895 260.97 157.762C236.598 173.799 182.753 194.385 140.925 203.65C127.451 206.637 116.52 208.148 108.91 208.148C104.927 208.148 101.859 207.723 99.8255 206.892C99.1645 206.62 98.6222 206.315 98.1985 205.958L98.3002 205.84L98.69 205.348C100.656 206.977 105.385 207.656 112.333 207.299C119.706 206.926 129.282 205.449 140.773 202.904C182.516 193.655 236.259 173.12 260.563 157.134C264.224 154.673 266.766 152.654 268.546 150.991Z" fill="white"/>
<path d="M127.909 218.689C122.994 218.689 119.35 218.078 117.164 216.873L117.536 216.212C122.248 218.825 134.773 218.418 151.908 215.091C186.753 208.32 231.208 191.519 251.038 177.637C255.343 174.634 258.207 171.952 259.563 169.695L260.207 170.086C258.8 172.444 255.851 175.194 251.479 178.248C231.581 192.181 186.99 209.033 152.06 215.838C142.264 217.722 134.078 218.689 127.909 218.689Z" fill="white"/>
<path d="M165.415 73.2644C165.229 73.468 165.076 73.7226 165.076 73.9941C165.076 74.656 165.89 74.9784 166.534 75.0972C170.28 75.776 173.957 75.4875 177.652 74.7238C182.669 73.6886 187.651 72.0595 192.821 72.0934C195.583 72.1104 198.21 72.7892 200.905 73.2813C203.346 73.7226 205.786 73.8074 208.244 73.4171C213.006 72.6365 217.396 70.3624 221.412 67.732C223.311 66.4931 225.226 65.2033 226.921 63.676C228.327 62.4032 229.666 60.9437 230.446 59.1788C231.158 57.5835 231.259 55.7847 230.429 54.2064C229.378 52.2548 227.107 51.2875 224.972 51.1687C222.429 51.0329 220.023 52.153 217.87 53.3918C215.565 54.7325 213.328 56.2089 211.023 57.5496C206.43 60.214 201.685 62.6578 196.804 64.7451C189.16 68.0035 181.516 69.7854 173.364 71.177C172.517 71.3297 171.381 71.3976 170.229 71.5334C168.364 71.8049 166.432 72.1783 165.415 73.2644Z" fill="url(#paint8_linear_6966_12826)"/>
<path d="M149.416 84.4478C149.857 85.2284 150.823 85.5678 151.721 85.6696C154.212 85.9751 156.653 86.3145 159.195 86.0939C160.805 85.9581 162.398 85.6527 163.958 85.1775C164.72 84.9399 165.483 84.6684 166.229 84.3629C166.924 84.0744 167.618 83.7859 168.229 83.3447C169.127 82.7168 170.059 81.5288 169.246 80.4597C168.788 79.8488 167.957 79.679 167.246 79.6281C166.33 79.5603 165.381 79.6621 164.466 79.713C160.619 79.9506 156.805 80.4936 153.043 81.3252C151.874 81.5797 149.162 82.0379 149.162 83.5483C149.145 83.8199 149.23 84.1084 149.416 84.4478Z" fill="url(#paint9_linear_6966_12826)"/>
<path d="M76.7759 42.2731C66.0137 44.5302 18.5586 76.0106 69.6237 74.4832C120.689 72.9559 104.385 55.7307 88.2499 58.7176C72.1151 61.7044 43.6251 63.7918 71.1152 49.4516L76.7759 42.2731Z" fill="url(#paint10_linear_6966_12826)"/>
<path d="M76.7759 42.2731C66.0137 44.5302 18.5586 76.0106 69.6237 74.4832C120.689 72.9559 104.385 55.7307 88.2499 58.7176C72.1151 61.7044 43.6251 63.7918 71.1152 49.4516L76.7759 42.2731Z" fill="url(#paint11_linear_6966_12826)"/>
<path d="M33.7949 198.236L59.6919 201.952V176.021L33.7949 172.305V198.236Z" fill="#43AEFC"/>
<path d="M76.9452 192.013L59.6919 201.957V176.026L76.9452 166.082V192.013Z" fill="#037DD6"/>
<path d="M51.0483 162.36L33.7949 172.288L59.6919 176.022L76.9453 166.077L51.0483 162.36Z" fill="#75C4FD"/>
<path d="M52.8412 193.73C56.0275 190.748 55.8486 185.371 52.4416 181.722C49.0346 178.073 43.6897 177.532 40.5033 180.515C37.3169 183.498 37.4958 188.874 40.9028 192.523C44.3098 196.173 49.6548 196.713 52.8412 193.73Z" fill="white"/>
<path d="M46.6755 182.238C46.6755 184.936 44.4892 186.837 41.7944 186.464C44.4892 186.82 46.6755 189.315 46.6755 192.013C46.6755 189.315 48.8619 187.414 51.5566 187.788C48.8619 187.431 46.6755 184.936 46.6755 182.238Z" fill="#43AEFC"/>
<path d="M0 160.577L24.6089 164.107V139.465L0 135.935V160.577Z" fill="#43AEFC"/>
<path d="M40.9979 154.672L24.6089 164.107V139.466L40.9979 130.03V154.672Z" fill="#037DD6"/>
<path d="M16.389 126.5L0 135.936L24.6089 139.466L40.9979 130.03L16.389 126.5Z" fill="#75C4FD"/>
<path d="M12.6434 151.89L11.9485 151.788C7.60971 151.16 4.08447 153.196 4.08447 156.353L20.5074 158.763C20.5074 155.589 16.9991 152.518 12.6434 151.89Z" fill="white"/>
<path d="M15.465 149.628C17.0898 148.115 16.9912 145.364 15.2449 143.483C13.4986 141.602 10.7658 141.304 9.14108 142.816C7.51634 144.329 7.61488 147.08 9.3612 148.96C11.1075 150.841 13.8403 151.14 15.465 149.628Z" fill="white"/>
<path d="M92.9443 181.859L118.858 185.576V159.645L92.9443 155.911V181.859Z" fill="#43AEFC"/>
<path d="M136.112 175.63L118.858 185.575V159.644L136.112 149.699V175.63Z" fill="#037DD6"/>
<path d="M110.198 145.982L92.9443 155.91L118.858 159.643L136.112 149.698L110.198 145.982Z" fill="#75C4FD"/>
<path d="M108.588 164.734L103.215 163.987C99.8762 163.529 97.1814 165.854 97.1814 169.197V172.727V173.372C97.1814 174.882 98.4017 176.274 99.9101 176.494C101.418 176.698 102.639 175.646 102.639 174.135V173.49L109.147 174.39V175.035C109.147 176.545 110.367 177.937 111.876 178.157C113.384 178.361 114.604 177.309 114.604 175.798V175.154V171.624C114.621 168.28 111.926 165.192 108.588 164.734Z" fill="white"/>
<path d="M102.011 170.757L100.723 170.57V166.225L102.011 166.412V170.757Z" fill="#43AEFC"/>
<path d="M103.537 169.432L99.1982 168.838V167.549L103.537 168.143V169.432Z" fill="#43AEFC"/>
<path d="M113.014 170.58C113.281 170.33 113.27 169.884 112.989 169.582C112.708 169.281 112.264 169.238 111.997 169.488C111.73 169.737 111.741 170.184 112.022 170.485C112.304 170.787 112.748 170.829 113.014 170.58Z" fill="#43AEFC"/>
<path d="M110.092 170.184C110.359 169.935 110.347 169.488 110.066 169.187C109.785 168.885 109.341 168.843 109.074 169.092C108.807 169.342 108.819 169.788 109.1 170.09C109.381 170.391 109.825 170.433 110.092 170.184Z" fill="#43AEFC"/>
<path d="M111.625 168.877C111.892 168.628 111.881 168.181 111.6 167.88C111.319 167.578 110.875 167.536 110.608 167.785C110.341 168.035 110.352 168.481 110.633 168.783C110.914 169.084 111.358 169.127 111.625 168.877Z" fill="#43AEFC"/>
<path d="M111.628 171.897C111.895 171.648 111.883 171.201 111.602 170.9C111.321 170.598 110.877 170.556 110.61 170.806C110.344 171.055 110.355 171.502 110.636 171.803C110.917 172.105 111.361 172.147 111.628 171.897Z" fill="#43AEFC"/>
<path d="M105.3 171.691L104.232 171.538C104.096 171.521 103.995 171.402 103.995 171.266C103.995 171.131 104.096 171.046 104.232 171.063L105.3 171.216C105.435 171.233 105.537 171.351 105.537 171.487C105.537 171.606 105.435 171.708 105.3 171.691Z" fill="#43AEFC"/>
<path d="M108.13 172.078L107.062 171.926C106.927 171.909 106.825 171.79 106.825 171.654C106.825 171.518 106.927 171.433 107.062 171.45L108.13 171.603C108.266 171.62 108.367 171.739 108.367 171.875C108.367 171.993 108.266 172.095 108.13 172.078Z" fill="#43AEFC"/>
<path d="M193.973 156.383L182.906 163.12V133.88L193.973 127.058V156.383Z" fill="#037DD6"/>
<path d="M163.941 127.058L151.247 133.88H182.906L193.973 127.058H163.941Z" fill="#75C4FD"/>
<path d="M182.889 133.882H151.247V163.241H182.889V133.882Z" fill="#43AEFC"/>
<path d="M161.941 141.093V155.535L173.754 148.305L161.941 141.093Z" fill="white"/>
<path d="M253.495 166.681L227.598 170.415V144.467L253.495 140.75V166.681Z" fill="#037DD6"/>
<path d="M210.345 160.473L227.598 170.418V144.47L210.345 134.542V160.473Z" fill="#43AEFC"/>
<path d="M236.242 130.807L253.495 140.752L227.598 144.469L210.345 134.541L236.242 130.807Z" fill="#75C4FD"/>
<path d="M245.292 161.849L233.835 156.86L245.292 147.865L245.987 148.884L236.411 156.402L245.987 160.576L245.292 161.849Z" fill="white"/>
<path d="M247.312 149.908C248.324 148.803 248.395 147.218 247.47 146.37C246.545 145.521 244.975 145.729 243.963 146.835C242.951 147.94 242.881 149.525 243.805 150.373C244.73 151.222 246.3 151.014 247.312 149.908Z" fill="white"/>
<path d="M247.311 162.823C248.323 161.718 248.394 160.134 247.469 159.285C246.545 158.436 244.975 158.645 243.963 159.75C242.951 160.856 242.88 162.44 243.805 163.288C244.729 164.137 246.299 163.929 247.311 162.823Z" fill="white"/>
<path d="M236.38 158.24C237.392 157.135 237.462 155.55 236.538 154.702C235.613 153.853 234.043 154.061 233.031 155.167C232.019 156.272 231.949 157.857 232.873 158.705C233.798 159.554 235.368 159.346 236.38 158.24Z" fill="white"/>
<path d="M271.206 172.169C269.8 176.005 266.529 178.873 262.986 180.757C258.631 183.065 253.597 183.794 248.784 184.558C243.801 185.356 238.089 188.461 233.276 189.972C228.446 191.482 223.734 193.383 219.209 195.623C217.429 196.505 215.718 197.625 213.701 197.897C211.887 198.151 209.955 197.523 209.311 195.657C208.989 194.774 209.311 192.364 213.091 190.566C218.446 188.02 221.955 186.696 227.7 185.152C227.751 185.135 227.802 185.118 227.853 185.101C230.666 184.015 233.479 182.929 236.293 181.86C241.123 180.01 246.072 178.364 250.75 176.141C252.241 175.428 253.665 174.63 255.004 173.714C255.038 173.697 255.055 173.68 255.089 173.663C256.156 172.984 257.885 171.338 259.207 170.031C260.376 168.877 261.359 167.553 262.122 166.094C262.749 164.906 263.495 163.464 263.969 162.496C264.02 162.462 264.071 162.428 264.122 162.394C265.054 161.902 266.139 161.733 267.257 161.953C271.969 162.903 272.613 168.351 271.206 172.169Z" fill="url(#paint12_linear_6966_12826)"/>
<path d="M213.091 200.663C212.243 199.186 210.532 199.645 209.227 200.12C208.667 200.323 208.125 200.544 207.583 200.799C205.667 201.291 203.871 202.156 202.227 203.31C201.176 204.04 199.769 205.228 200.702 206.62C201.363 207.604 202.752 207.638 203.803 207.4C205.295 207.078 206.786 206.569 208.21 206.009C209.549 205.483 211.074 204.973 212.209 204.057C213.209 203.276 213.786 201.868 213.091 200.663Z" fill="url(#paint13_linear_6966_12826)"/>
<path d="M213.667 2.46215C211.87 0.663266 209.057 -0.134352 206.65 0.0183837C205.718 0.086266 204.582 0.408707 204.125 1.52877C202.989 4.41376 206.328 4.41376 205.921 6.94238C205.769 7.9097 205.159 8.72429 204.532 9.38614C199.684 14.4264 193.6 15.7841 187.651 17.7357C187.363 17.8205 187.075 17.9223 186.787 18.0072C186.753 18.0242 186.719 18.0242 186.702 18.0411C184.397 18.9236 181.177 19.4667 179.923 22.3008C178.499 25.5252 180.279 28.8005 182.668 30.1072C185.329 31.5667 188.566 31.4479 191.515 30.9218C194.075 30.4806 196.634 29.683 199.074 28.529C206.243 25.2027 223.751 12.5936 213.667 2.46215Z" fill="url(#paint14_linear_6966_12826)"/>
<path d="M199.414 4.1767C198.092 1.34261 193.719 1.51232 191.346 2.70026C189.753 3.49788 188.041 5.02523 187.567 6.84108C186.719 8.11388 186.397 9.52243 187.262 10.8971C188.889 13.4766 192.414 12.5941 194.821 11.2874C197.719 10.2522 200.837 7.23141 199.414 4.1767Z" fill="url(#paint15_linear_6966_12826)"/>
<path d="M110.757 105.898L113.215 105.66L112.249 103.081L110.486 103.251L110.757 105.898Z" fill="#B77B6A"/>
<path d="M111.995 97.1218C112.639 97.3933 113.198 98.191 113.605 98.768C114.011 99.345 114.316 99.9729 114.621 100.601C114.842 101.076 115.045 101.585 114.994 102.111C114.944 102.756 114.503 103.316 113.961 103.655C112.656 104.47 110.808 104.012 109.842 102.824C108.91 101.67 108.876 100.16 109.13 98.768C109.368 97.5121 110.842 96.6297 111.995 97.1218Z" fill="#B77B6A"/>
<path d="M109.757 104.42C110.689 105.048 110.91 103.86 110.74 102.791C111.045 101.892 109.842 100.958 110.673 100.178C111.045 99.8724 111.537 99.7536 111.91 99.4481C112.283 99.1426 112.639 98.1414 112.639 97.6492C112.977 98.0565 113.367 98.8541 113.825 99.1256C113.689 98.1074 113.079 97.1061 112.164 96.6479C111.249 96.1728 110.113 96.1897 109.215 96.6649C108.571 97.0043 108.046 97.5644 107.418 97.9207C106.927 98.1923 106.351 98.345 105.791 98.345C105.063 98.345 104.317 98.1074 103.605 98.2092C102.792 98.328 102.097 98.8711 101.3 99.1426C100.487 99.4142 99.5714 99.3972 98.7917 99.0747C98.3002 100.161 98.4697 101.502 99.046 102.537C99.8086 103.894 101.368 104.743 101.842 106.219C102.114 107.068 101.995 108.052 102.419 108.85C102.825 109.613 103.622 110.038 104.402 110.411L109.757 104.42Z" fill="#2E3446"/>
<path d="M110.469 102.843L110.995 102.741L110.757 101.757C110.656 101.35 110.232 101.095 109.825 101.214L109.757 101.231C109.351 101.333 109.181 101.825 109.435 102.164L109.69 102.521C109.859 102.775 110.164 102.894 110.469 102.843Z" fill="#B77B6A"/>
<path d="M114.266 101.264C114.266 101.264 113.367 102.163 112.859 102.027C112.859 102.027 113.605 102.876 114.333 101.96L114.266 101.264Z" fill="white"/>
<path d="M125.366 99.7697C125.587 99.4303 125.688 98.8533 125.722 98.6326C125.756 98.412 125.841 98.0217 126.282 97.7332C126.722 97.4447 126.536 97.8011 126.485 98.0387C126.434 98.2763 126.655 98.5478 126.604 98.8702C126.553 99.1927 126.129 100.007 125.79 100.126C125.451 100.245 125.366 99.7697 125.366 99.7697Z" fill="#E88F97"/>
<path d="M126.214 98.5327C126.638 98.1594 127.316 97.4975 127.807 97.1411C128.502 96.632 128.417 96.9205 127.909 97.5654C127.587 97.9727 127.367 98.2781 127.367 98.2781L127.468 98.3291C127.468 98.3291 127.858 98.0745 128.214 97.8369C128.57 97.5993 128.824 97.4975 129.061 97.4466C129.299 97.3957 129.197 98.1254 129.129 98.4818C129.061 98.8382 128.146 100.009 127.909 100.145C127.672 100.281 127.451 100.433 126.706 100.62C125.723 101.044 125.485 100.111 125.485 100.111L126.214 98.5327Z" fill="#E88F97"/>
<path d="M113.452 105.557L113.74 104.454L112.401 104.522L113.452 105.557Z" fill="#FFD33D"/>
<path d="M96.1477 108.51C96.1477 108.51 95.9274 108.731 95.8596 108.884C95.7918 109.036 95.7918 109.715 95.4529 109.885C95.097 110.055 94.6732 110.123 94.5377 110.123C94.4021 110.123 94.2326 109.919 94.1648 109.885C94.097 109.851 93.7072 110.004 93.4191 109.766C93.1309 109.529 93.1648 109.036 93.2665 108.408C93.3682 107.781 93.3513 107.594 94.0123 107.305C94.6732 107.017 95.7579 106.644 95.7579 106.644L96.1477 108.51Z" fill="#E88F97"/>
<path d="M111.011 106.187L113.537 105.932L112.554 103.285L110.74 103.472L111.011 106.187Z" fill="#E88F97"/>
<path d="M112.282 97.1575C112.943 97.446 113.52 98.2605 113.943 98.8545C114.35 99.4485 114.672 100.076 114.977 100.738C115.214 101.23 115.418 101.756 115.367 102.283C115.299 102.944 114.858 103.521 114.299 103.878C112.96 104.709 111.062 104.251 110.062 103.029C109.096 101.841 109.079 100.297 109.333 98.8545C109.588 97.5647 111.113 96.6483 112.282 97.1575Z" fill="#E88F97"/>
<path d="M109.706 105.286L110.74 105.15C111.706 105.795 111.164 104.081 110.994 102.995C111.316 102.079 110.062 101.111 110.927 100.314C111.299 99.9912 111.808 99.8724 112.198 99.5669C112.587 99.2614 112.943 98.2093 112.96 97.7171C113.299 98.1414 113.706 98.956 114.181 99.2445C114.045 98.2093 113.418 97.1741 112.469 96.6989C111.537 96.2237 110.367 96.2237 109.435 96.7159C109.35 96.7668 109.283 96.8007 109.215 96.8516C109.215 96.8516 109.198 96.8516 109.198 96.8686C106.961 98.4299 105.469 103.419 109.706 105.286Z" fill="#2E3446"/>
<path d="M110.723 103.03L111.266 102.928L111.011 101.926C110.91 101.502 110.486 101.248 110.062 101.366L109.995 101.383C109.571 101.502 109.401 101.994 109.656 102.351L109.927 102.707C110.096 102.962 110.418 103.097 110.723 103.03Z" fill="#E88F97"/>
<path d="M114.621 101.418C114.621 101.418 113.706 102.351 113.164 102.198C113.164 102.198 113.943 103.081 114.689 102.13L114.621 101.418Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M95.2097 106.836C101.569 103.688 108.682 104.905 112.69 105.773L112.281 107.688C108.291 106.825 101.777 105.768 96.0688 108.594L95.2097 106.836Z" fill="#FFD33D"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M115.476 107.644C114.516 107.617 113.595 107.591 112.594 107.704L112.377 105.757C113.524 105.628 114.591 105.658 115.545 105.686C115.693 105.69 115.838 105.694 115.981 105.697C117.067 105.724 118.007 105.712 118.948 105.475C120.758 105.019 122.836 103.655 125.648 99.2283L127.288 100.284C124.355 104.902 121.924 106.745 119.421 107.376C118.205 107.682 117.037 107.683 115.934 107.656C115.78 107.652 115.627 107.648 115.476 107.644Z" fill="#FFD33D"/>
<path d="M113.231 105.102C113.231 105.102 120.892 116.32 117.384 128.64C117.384 128.64 102.198 132.119 94.9104 114.3L110.74 105.153L113.231 105.102Z" fill="#FFD33D"/>
<path d="M120.163 139.634C120.163 139.634 118.773 145.354 118.757 146.762C118.74 148.171 121.248 152.43 121.248 152.43L114.536 149.002L116.35 139.312L120.163 139.634Z" fill="#FFD33D"/>
<path d="M109.656 113.723L111.401 115.115C119.316 121.411 122.994 131.78 120.791 141.521L114.621 139.994C115.909 134.359 116.537 126.383 110.028 120.155L108.808 119.035C107.808 118.102 107.537 116.608 108.181 115.403L109.656 113.723Z" fill="#2E3446"/>
<path d="M89.1311 120.355C89.1311 120.355 85.1483 116.027 83.9619 115.23C82.7755 114.432 77.8774 114.178 77.8774 114.178L84.4534 110.478L91.5208 117.351L89.1311 120.355Z" fill="#FFD33D"/>
<path d="M108.164 115.401C108.13 115.452 105.503 120.798 100.334 120.882C97.8085 120.916 94.7917 118.795 92.5376 116.063L87.6565 120.085C90.8767 123.971 94.5714 125.906 98.6051 125.838C103.825 125.753 108.13 122.24 110.333 120.034C112.096 118.337 112.977 116.114 112.977 116.114L108.164 115.401Z" fill="#2E3446"/>
<path d="M112.791 105.387C112.791 105.387 116.333 113.533 114.876 118.437L107.435 116.418L112.791 105.387Z" fill="#F66A0A"/>
<path d="M103.317 99.9914C103.741 98.0568 106.961 98.3962 108.978 100.178L108.893 102.418C103.859 104.624 102.876 101.926 103.317 99.9914Z" fill="#2E3446"/>
<path d="M112.791 105.386C112.774 105.353 112.096 103.944 112.096 103.944L110.486 104.47L110.689 105.403L112.791 105.386Z" fill="#FFD33D"/>
<path d="M112.859 106.151C113.186 106.151 113.452 105.885 113.452 105.557C113.452 105.229 113.186 104.963 112.859 104.963C112.531 104.963 112.266 105.229 112.266 105.557C112.266 105.885 112.531 106.151 112.859 106.151Z" fill="#2E3446"/>
<path d="M311.696 145.492L283.409 149.548V121.241L311.696 117.168V145.492Z" fill="#037DD6"/>
<path d="M264.58 138.686L283.409 149.547V121.24L264.58 110.379V138.686Z" fill="#43AEFC"/>
<path d="M292.849 106.321L311.696 117.165L283.409 121.238L264.58 110.377L292.849 106.321Z" fill="#75C4FD"/>
<path d="M297.544 123.665C293.815 124.191 290.781 127.636 290.781 131.37C290.781 135.103 297.544 143.045 297.544 143.045C297.544 143.045 304.306 133.22 304.306 129.486C304.306 125.753 301.289 123.139 297.544 123.665ZM297.544 132.592C295.951 132.812 294.663 131.709 294.663 130.114C294.663 128.519 295.951 127.042 297.544 126.822C299.137 126.601 300.425 127.704 300.425 129.299C300.425 130.895 299.137 132.354 297.544 132.592Z" fill="white"/>
<path d="M136.095 199.204C134.722 198.966 133.434 199.407 132.146 199.832C130.485 200.392 128.807 200.867 127.112 201.274C123.807 202.072 120.417 202.462 117.028 202.615C113.18 202.785 109.299 202.632 105.486 202.14C102.689 201.783 99.893 201.461 97.4016 202.615C96.4186 202.988 95.5542 203.548 95.3339 204.583C95.1474 205.5 95.6051 206.331 96.4186 206.756C96.8931 206.993 97.4185 207.078 97.8931 207.333C98.1812 207.485 98.3676 207.757 98.4185 208.062C98.4524 208.215 98.4863 208.351 98.5371 208.487C98.8591 209.386 99.6387 210.082 100.452 210.523C102.91 211.881 106.045 211.609 108.74 211.423C114.774 211.015 120.705 210.099 126.553 208.555C128.264 208.096 129.959 207.604 131.637 207.044C133.146 206.535 134.79 206.06 136.196 205.296C137.4 204.634 138.603 203.48 138.603 202.021C138.586 200.578 137.467 199.441 136.095 199.204Z" fill="url(#paint16_linear_6966_12826)"/>
<path d="M86.6904 190.141C84.8769 189.48 82.8431 188.852 80.8093 188.614C80.4195 188.563 80.0128 188.614 79.6738 188.801C79.4535 188.92 79.2501 189.089 79.0976 189.327C78.9111 189.615 78.7925 189.938 78.7247 190.277C78.7078 192.704 81.0127 194.673 82.877 196.047C84.0465 196.913 85.3515 197.846 86.809 198.084C87.6056 198.219 88.5717 198.084 89.1818 197.558C90.3173 197.116 91.6054 196.522 92.0969 195.334C93.3172 192.382 88.6395 190.854 86.6904 190.141Z" fill="url(#paint17_linear_6966_12826)"/>
<path d="M119.994 215.851C119.909 215.563 119.723 215.376 119.452 215.257C119.435 215.24 119.418 215.223 119.384 215.206C119.282 215.155 119.18 215.105 119.079 215.071C118.994 214.952 118.841 214.867 118.655 214.867C117.553 214.867 115.723 214.663 114.909 215.614C114.537 216.055 114.537 216.717 114.977 217.124C115.452 217.548 116.214 217.565 116.808 217.548C117.638 217.497 120.401 217.192 119.994 215.851Z" fill="url(#paint18_linear_6966_12826)"/>
<path d="M294.68 42.6129C295.7 42.6129 296.527 41.7847 296.527 40.7631C296.527 39.7415 295.7 38.9133 294.68 38.9133C293.659 38.9133 292.832 39.7415 292.832 40.7631C292.832 41.7847 293.659 42.6129 294.68 42.6129Z" fill="url(#paint19_linear_6966_12826)"/>
<path d="M46.7434 149.718C47.7636 149.718 48.5907 148.89 48.5907 147.868C48.5907 146.847 47.7636 146.019 46.7434 146.019C45.7231 146.019 44.896 146.847 44.896 147.868C44.896 148.89 45.7231 149.718 46.7434 149.718Z" fill="url(#paint20_linear_6966_12826)"/>
<path d="M159.704 188.392C160.724 188.392 161.551 187.563 161.551 186.542C161.551 185.52 160.724 184.692 159.704 184.692C158.684 184.692 157.856 185.52 157.856 186.542C157.856 187.563 158.684 188.392 159.704 188.392Z" fill="url(#paint21_linear_6966_12826)"/>
<path d="M17.0329 175.918C18.0532 175.918 18.8803 175.09 18.8803 174.069C18.8803 173.047 18.0532 172.219 17.0329 172.219C16.0126 172.219 15.1855 173.047 15.1855 174.069C15.1855 175.09 16.0126 175.918 17.0329 175.918Z" fill="url(#paint22_linear_6966_12826)"/>
<path d="M279.02 101.607C280.04 101.607 280.867 100.779 280.867 99.7571C280.867 98.7355 280.04 97.9073 279.02 97.9073C277.999 97.9073 277.172 98.7355 277.172 99.7571C277.172 100.779 277.999 101.607 279.02 101.607Z" fill="url(#paint23_linear_6966_12826)"/>
<path d="M274.799 153.113C275.82 153.113 276.647 152.285 276.647 151.264C276.647 150.242 275.82 149.414 274.799 149.414C273.779 149.414 272.952 150.242 272.952 151.264C272.952 152.285 273.779 153.113 274.799 153.113Z" fill="url(#paint24_linear_6966_12826)"/>
<path d="M328 95.5088L301.154 99.3781V72.4798L328 68.6274V95.5088Z" fill="#037DD6"/>
<path d="M283.257 89.0627L301.154 99.3809V72.4825L283.257 62.1644V89.0627Z" fill="#43AEFC"/>
<path d="M310.12 58.3107L328 68.6118L301.154 72.4811L283.257 62.163L310.12 58.3107Z" fill="#75C4FD"/>
<path d="M320.356 84.2271C320.39 83.9725 320.407 83.701 320.407 83.4464C320.407 83.1749 320.39 82.9203 320.339 82.6658L322.034 81.1045C322.187 80.9687 322.221 80.7311 322.136 80.5784L320.543 78.0328C320.441 77.8631 320.238 77.8291 320.051 77.931L318.068 79.0171C317.645 78.7625 317.204 78.5589 316.712 78.4231L316.407 76.3357C316.373 76.1321 316.204 76.0303 316.001 76.0472L312.797 76.5054C312.594 76.5394 312.441 76.6921 312.408 76.8958L312.102 79.068C311.611 79.3395 311.17 79.6789 310.747 80.0353L308.764 79.5262C308.577 79.4753 308.374 79.5771 308.272 79.7808L306.679 82.7846C306.577 82.9712 306.611 83.1919 306.781 83.2767L308.476 84.3628C308.442 84.6174 308.408 84.8889 308.408 85.1604C308.408 85.432 308.425 85.6865 308.476 85.9411L306.781 87.5024C306.628 87.6381 306.594 87.8757 306.679 88.0285L308.272 90.5741C308.374 90.7438 308.577 90.7777 308.764 90.6759L310.747 89.5898C311.17 89.8443 311.611 90.048 312.102 90.1837L312.408 92.2711C312.441 92.4578 312.611 92.5766 312.814 92.5596L316.018 92.1014C316.221 92.0675 316.39 91.9147 316.407 91.7111L316.712 89.5388C317.204 89.2673 317.645 88.9279 318.068 88.5715L320.051 89.0806C320.238 89.1316 320.441 89.0297 320.543 88.8261L322.136 85.8223C322.238 85.6356 322.204 85.415 322.034 85.3301L320.356 84.2271ZM314.39 87.2987C312.746 87.5363 311.391 86.3823 311.391 84.7192C311.391 83.0561 312.746 81.5287 314.39 81.2911C316.034 81.0536 317.39 82.2076 317.39 83.8707C317.39 85.5338 316.051 87.0611 314.39 87.2987Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_6966_12826" x1="159.628" y1="107.412" x2="223.004" y2="242.728" gradientUnits="userSpaceOnUse">
<stop offset="0.0101602" stop-color="#F2F2F2"/>
<stop offset="0.9999" stop-color="#CCCCCC"/>
</linearGradient>
<linearGradient id="paint1_linear_6966_12826" x1="88.7838" y1="52.5761" x2="204.486" y2="300.54" gradientUnits="userSpaceOnUse">
<stop stop-color="#E6E6E6"/>
<stop offset="1" stop-color="#808080"/>
</linearGradient>
<linearGradient id="paint2_linear_6966_12826" x1="41.7906" y1="74.5032" x2="157.493" y2="322.467" gradientUnits="userSpaceOnUse">
<stop stop-color="#E6E6E6"/>
<stop offset="1" stop-color="#808080"/>
</linearGradient>
<linearGradient id="paint3_linear_6966_12826" x1="165.179" y1="189.093" x2="223.556" y2="226.144" gradientUnits="userSpaceOnUse">
<stop stop-color="#E6E6E6"/>
<stop offset="1" stop-color="#808080"/>
</linearGradient>
<linearGradient id="paint4_linear_6966_12826" x1="167.632" y1="15.7845" x2="283.334" y2="263.748" gradientUnits="userSpaceOnUse">
<stop stop-color="#E6E6E6"/>
<stop offset="1" stop-color="#808080"/>
</linearGradient>
<linearGradient id="paint5_linear_6966_12826" x1="195.362" y1="2.84379" x2="311.064" y2="250.808" gradientUnits="userSpaceOnUse">
<stop stop-color="#E6E6E6"/>
<stop offset="1" stop-color="#808080"/>
</linearGradient>
<linearGradient id="paint6_linear_6966_12826" x1="217.002" y1="-7.25108" x2="332.704" y2="240.713" gradientUnits="userSpaceOnUse">
<stop stop-color="#E6E6E6"/>
<stop offset="1" stop-color="#808080"/>
</linearGradient>
<linearGradient id="paint7_linear_6966_12826" x1="45.9648" y1="131.68" x2="270.909" y2="52.869" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint8_linear_6966_12826" x1="141.1" y1="63.3402" x2="244.334" y2="63.3402" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFE566"/>
<stop offset="1" stop-color="#FFB0EB"/>
</linearGradient>
<linearGradient id="paint9_linear_6966_12826" x1="141.1" y1="82.879" x2="244.334" y2="82.879" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFE566"/>
<stop offset="1" stop-color="#FFB0EB"/>
</linearGradient>
<linearGradient id="paint10_linear_6966_12826" x1="75.3517" y1="65.4367" x2="81.8393" y2="39.5203" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFE566"/>
<stop offset="1" stop-color="#FFB0EB"/>
</linearGradient>
<linearGradient id="paint11_linear_6966_12826" x1="75.3517" y1="65.4367" x2="81.8393" y2="39.5203" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFE566"/>
<stop offset="1" stop-color="#FFB0EB"/>
</linearGradient>
<linearGradient id="paint12_linear_6966_12826" x1="297.367" y1="147.49" x2="230.403" y2="190.447" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFE566"/>
<stop offset="1" stop-color="#FFB0EB"/>
</linearGradient>
<linearGradient id="paint13_linear_6966_12826" x1="64.1903" y1="239.535" x2="167.607" y2="213.599" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFE566"/>
<stop offset="1" stop-color="#FFB0EB"/>
</linearGradient>
<linearGradient id="paint14_linear_6966_12826" x1="65.5451" y1="67.577" x2="225.504" y2="5.57676" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFE566"/>
<stop offset="1" stop-color="#FFB0EB"/>
</linearGradient>
<linearGradient id="paint15_linear_6966_12826" x1="61.8447" y1="58.0293" x2="221.803" y2="-3.97067" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFE566"/>
<stop offset="1" stop-color="#FFB0EB"/>
</linearGradient>
<linearGradient id="paint16_linear_6966_12826" x1="59.0001" y1="218.838" x2="162.416" y2="192.903" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFE566"/>
<stop offset="1" stop-color="#FFB0EB"/>
</linearGradient>
<linearGradient id="paint17_linear_6966_12826" x1="54.4732" y1="200.787" x2="157.889" y2="174.851" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFE566"/>
<stop offset="1" stop-color="#FFB0EB"/>
</linearGradient>
<linearGradient id="paint18_linear_6966_12826" x1="61.8207" y1="230.084" x2="165.237" y2="204.148" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFE566"/>
<stop offset="1" stop-color="#FFB0EB"/>
</linearGradient>
<linearGradient id="paint19_linear_6966_12826" x1="292.818" y1="40.76" x2="296.527" y2="40.76" gradientUnits="userSpaceOnUse">
<stop stop-color="#75C4FD"/>
<stop offset="1" stop-color="#75C4FD"/>
</linearGradient>
<linearGradient id="paint20_linear_6966_12826" x1="44.896" y1="147.861" x2="48.6052" y2="147.861" gradientUnits="userSpaceOnUse">
<stop stop-color="#75C4FD"/>
<stop offset="1" stop-color="#75C4FD"/>
</linearGradient>
<linearGradient id="paint21_linear_6966_12826" x1="157.852" y1="186.54" x2="161.562" y2="186.54" gradientUnits="userSpaceOnUse">
<stop stop-color="#75C4FD"/>
<stop offset="1" stop-color="#75C4FD"/>
</linearGradient>
<linearGradient id="paint22_linear_6966_12826" x1="15.1708" y1="174.073" x2="18.88" y2="174.073" gradientUnits="userSpaceOnUse">
<stop stop-color="#75C4FD"/>
<stop offset="1" stop-color="#75C4FD"/>
</linearGradient>
<linearGradient id="paint23_linear_6966_12826" x1="277.168" y1="99.7566" x2="280.878" y2="99.7566" gradientUnits="userSpaceOnUse">
<stop stop-color="#75C4FD"/>
<stop offset="1" stop-color="#75C4FD"/>
</linearGradient>
<linearGradient id="paint24_linear_6966_12826" x1="272.938" y1="151.261" x2="276.647" y2="151.261" gradientUnits="userSpaceOnUse">
<stop stop-color="#75C4FD"/>
<stop offset="1" stop-color="#75C4FD"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 51 KiB

BIN
app/images/reveal-srp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,19 @@
<svg width="1024" height="606" viewBox="0 0 1024 606" fill="none" xmlns="http://www.w3.org/2000/svg">
<g style="mix-blend-mode:multiply" opacity="0.1">
<path d="M423.504 537.172C403.6 534.714 382.471 538.401 364.404 529.183C327.352 510.439 329.801 451.135 294.893 428.396C275.907 415.798 250.185 418.256 229.974 428.704C209.764 439.151 194.453 456.973 180.367 474.795C149.133 515.048 122.492 559.296 101.669 606.001L498.834 606.001C489.035 571.587 459.332 541.781 423.504 537.172Z" fill="#666666"/>
<path d="M0.00689697 606.001L0.00691613 386.914C56.351 345.125 112.695 303.028 171.183 263.39C210.991 236.349 251.412 210.538 293.67 186.571C348.789 155.229 425.344 153.078 463.621 207.466C485.669 238.501 489.956 280.597 500.98 318.392C512.003 356.187 536.501 395.518 571.716 396.44C604.175 397.362 629.591 365.405 645.821 333.448C661.744 301.492 674.605 265.233 701.552 244.646C734.318 219.757 779.638 226.21 813.322 249.255C821.896 255.093 829.858 261.853 837.207 269.535C858.948 291.352 876.403 319.007 892.633 346.354C922.03 396.44 926.317 426.246 952.958 478.175C975.311 521.501 997.665 565.441 1024 605.694L498.53 605.694C488.731 571.279 459.334 541.474 423.2 536.865C403.296 534.406 382.167 538.094 364.406 528.875C327.354 510.132 329.498 450.827 294.895 428.089C275.909 415.491 250.187 417.949 229.977 428.396C209.766 438.844 194.455 456.666 180.369 474.488C149.135 514.741 122.494 558.988 101.671 605.694L0.006897 605.694L0.00689697 606.001Z" fill="url(#paint0_linear_6094_102467)"/>
<path d="M171.483 263.436C187.1 250.838 202.104 238.24 215.578 225.334C273.147 170.332 327.96 112.257 379.405 51.4166C405.433 20.6892 445.854 -14.0328 480.457 6.86188C491.787 13.9292 499.442 25.9129 504.648 38.2039C516.284 65.8586 518.734 95.9715 523.633 125.47C528.533 154.968 537.107 185.388 557.93 206.898C578.753 228.407 614.886 237.318 638.771 219.496C658.982 204.439 664.494 176.785 680.723 157.426C705.221 127.928 752.072 124.241 784.837 143.906C817.603 163.572 835.976 201.981 838.732 240.083C839.344 250.223 839.038 260.363 838.119 270.196C830.77 262.822 822.808 256.062 814.234 249.916C780.55 226.87 735.23 220.418 702.465 245.307C675.517 265.894 662.656 302.153 646.733 334.109C630.81 366.066 605.087 398.022 572.628 397.1C537.413 396.179 512.916 356.848 501.892 319.053C490.868 281.258 486.581 239.469 464.533 208.127C426.256 153.432 349.702 155.583 294.582 187.232C251.712 210.585 210.985 236.396 171.483 263.436Z" fill="url(#paint1_linear_6094_102467)"/>
<path d="M916.744 22.243C899.228 28.0088 899.857 78.0428 918.149 133.997C936.441 189.952 965.469 230.638 982.985 224.872C1000.5 219.106 999.872 169.072 981.58 113.118C963.288 57.1633 934.26 16.4773 916.744 22.243Z" fill="white"/>
</g>
<defs>
<linearGradient id="paint0_linear_6094_102467" x1="1024.16" y1="385.234" x2="0.133924" y2="385.234" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFE566"/>
<stop offset="1" stop-color="#FFB0EB"/>
</linearGradient>
<linearGradient id="paint1_linear_6094_102467" x1="838.416" y1="198.467" x2="171.367" y2="198.467" gradientUnits="userSpaceOnUse">
<stop stop-color="#75C4FD"/>
<stop offset="0.1001" stop-color="#82C3F7"/>
<stop offset="1" stop-color="#F1B9BE"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -4,6 +4,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8">
<title>MetaMask Notification</title>
{{@if(it.isMMI)}}
<title>MetaMask Institutional</title>
{{/if}}
<style>
#app-content {
display: flex;

View File

@ -60,6 +60,14 @@ function importAllScripts() {
loadFile('./globalthis.js');
loadFile('./sentry-install.js');
// eslint-disable-next-line no-undef
const isWorker = !self.document;
if (!isWorker) {
loadFile('./snow.js');
}
loadFile('./use-snow.js');
// Always apply LavaMoat in e2e test builds, so that we can capture initialization stats
if (testMode || applyLavaMoat) {
loadFile('./runtime-lavamoat.js');

View File

@ -462,7 +462,7 @@ export function setupController(
setupEnsIpfsResolver({
getCurrentChainId: () =>
controller.networkController.store.getState().providerConfig.chainId,
controller.networkController.state.providerConfig.chainId,
getIpfsGateway: controller.preferencesController.getIpfsGateway.bind(
controller.preferencesController,
),

View File

@ -485,9 +485,11 @@ const onMessageSetUpExtensionStreams = (msg) => {
/**
* This listener destroys the extension streams when the extension port is disconnected,
* so that streams may be re-established later when the extension port is reconnected.
*
* @param {Error} [err] - Stream connection error
*/
const onDisconnectDestroyStreams = () => {
const err = checkForLastError();
const onDisconnectDestroyStreams = (err) => {
const lastErr = err || checkForLastError();
extensionPort.onDisconnect.removeListener(onDisconnectDestroyStreams);
@ -501,8 +503,8 @@ const onDisconnectDestroyStreams = () => {
* may cause issues. We suspect that this is a chromium bug as this event should only be called
* once the port and connections are ready. Delay time is arbitrary.
*/
if (err) {
console.warn(`${err} Resetting the streams.`);
if (lastErr) {
console.warn(`${lastErr} Resetting the streams.`);
setTimeout(setupExtensionStreams, 1000);
}
};
@ -630,6 +632,18 @@ const start = () => {
injectScript(inpageBundle);
}
initStreams();
// https://bugs.chromium.org/p/chromium/issues/detail?id=1457040
// Temporary workaround for chromium bug that breaks the content script <=> background connection
// for prerendered pages. This resets potentially broken extension streams if a page transitions
// from the prerendered state to the active state.
if (document.prerendering) {
document.addEventListener('prerenderingchange', () => {
onDisconnectDestroyStreams(
new Error('Prerendered page has become active.'),
);
});
}
}
};

View File

@ -13,6 +13,7 @@ import {
POLLING_TOKEN_ENVIRONMENT_TYPES,
ORIGIN_METAMASK,
} from '../../../shared/constants/app';
import { DEFAULT_AUTO_LOCK_TIME_LIMIT } from '../../../shared/constants/preferences';
export default class AppStateController extends EventEmitter {
/**
@ -32,7 +33,7 @@ export default class AppStateController extends EventEmitter {
this.onInactiveTimeout = onInactiveTimeout || (() => undefined);
this.store = new ObservableStore({
timeoutMinutes: 0,
timeoutMinutes: DEFAULT_AUTO_LOCK_TIME_LIMIT,
connectedStatusPopoverHasBeenShown: true,
defaultHomeActiveTabName: null,
browserEnvironment: {},

View File

@ -31,7 +31,7 @@ export default class BackupController {
}
if (network) {
this.networkController.store.updateState(network);
this.networkController.loadBackup(network);
}
if (preferences || addressBook || network) {
@ -48,7 +48,7 @@ export default class BackupController {
addressBook: { ...this.addressBookController.state },
network: {
networkConfigurations:
this.networkController.store.getState().networkConfigurations,
this.networkController.state.networkConfigurations,
},
};

View File

@ -57,18 +57,15 @@ function getMockAddressBookController() {
}
function getMockNetworkController() {
const mcState = {
const state = {
networkConfigurations: {},
update: (store) => (mcState.store = store),
};
mcState.store = {
getState: sinon.stub().returns(mcState),
updateState: (store) => (mcState.store = store),
const loadBackup = ({ networkConfigurations }) => {
Object.assign(state, { networkConfigurations });
};
return mcState;
return { state, loadBackup };
}
const jsonData = JSON.stringify({
@ -174,28 +171,28 @@ describe('BackupController', function () {
it('should restore backup', async function () {
const backupController = getBackupController();
backupController.restoreUserData(jsonData);
await backupController.restoreUserData(jsonData);
// check networks backup
assert.equal(
backupController.networkController.store.networkConfigurations[
backupController.networkController.state.networkConfigurations[
'network-configuration-id-1'
].chainId,
'0x539',
);
assert.equal(
backupController.networkController.store.networkConfigurations[
backupController.networkController.state.networkConfigurations[
'network-configuration-id-2'
].chainId,
'0x38',
);
assert.equal(
backupController.networkController.store.networkConfigurations[
backupController.networkController.state.networkConfigurations[
'network-configuration-id-3'
].chainId,
'0x61',
);
assert.equal(
backupController.networkController.store.networkConfigurations[
backupController.networkController.state.networkConfigurations[
'network-configuration-id-4'
].chainId,
'0x89',

View File

@ -33,8 +33,10 @@ export default class DetectTokensController {
* @param config.tokensController
* @param config.assetsContractController
* @param config.trackMetaMetricsEvent
* @param config.messenger
*/
constructor({
messenger,
interval = DEFAULT_INTERVAL,
preferences,
network,
@ -44,6 +46,7 @@ export default class DetectTokensController {
assetsContractController = null,
trackMetaMetricsEvent,
} = {}) {
this.messenger = messenger;
this.assetsContractController = assetsContractController;
this.tokensController = tokensController;
this.preferences = preferences;
@ -59,7 +62,7 @@ export default class DetectTokensController {
});
this.hiddenTokens = this.tokensController?.state.ignoredTokens;
this.detectedTokens = this.tokensController?.state.detectedTokens;
this.chainId = this.getChainIdFromNetworkStore(network);
this.chainId = this.getChainIdFromNetworkStore();
this._trackMetaMetricsEvent = trackMetaMetricsEvent;
preferences?.store.subscribe(({ selectedAddress, useTokenDetection }) => {
@ -81,6 +84,13 @@ export default class DetectTokensController {
this.detectedTokens = detectedTokens;
},
);
messenger.subscribe('NetworkController:stateChange', () => {
if (this.chainId !== this.getChainIdFromNetworkStore()) {
const chainId = this.getChainIdFromNetworkStore();
this.chainId = chainId;
this.restartTokenDetection({ chainId: this.chainId });
}
});
}
/**
@ -93,7 +103,7 @@ export default class DetectTokensController {
async detectNewTokens({ selectedAddress, chainId } = {}) {
const addressAgainstWhichToDetect = selectedAddress ?? this.selectedAddress;
const chainIdAgainstWhichToDetect =
chainId ?? this.getChainIdFromNetworkStore(this._network);
chainId ?? this.getChainIdFromNetworkStore();
if (!this.isActive) {
return;
}
@ -208,8 +218,8 @@ export default class DetectTokensController {
this.interval = DEFAULT_INTERVAL;
}
getChainIdFromNetworkStore(network) {
return network?.store.getState().providerConfig.chainId;
getChainIdFromNetworkStore() {
return this.network?.state.providerConfig.chainId;
}
/* eslint-disable accessor-pairs */
@ -226,23 +236,6 @@ export default class DetectTokensController {
}, interval);
}
/**
* @type {object}
*/
set network(network) {
if (!network) {
return;
}
this._network = network;
this._network.store.subscribe(() => {
if (this.chainId !== this.getChainIdFromNetworkStore(network)) {
const chainId = this.getChainIdFromNetworkStore(network);
this.chainId = chainId;
this.restartTokenDetection({ chainId: this.chainId });
}
});
}
/**
* In setter when isUnlocked is updated to true, detectNewTokens and restart polling
*

View File

@ -10,12 +10,19 @@ import {
AssetsContractController,
} from '@metamask/assets-controllers';
import { toHex } from '@metamask/controller-utils';
import { NetworkController } from '@metamask/network-controller';
import { NETWORK_TYPES } from '../../../shared/constants/network';
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils';
import DetectTokensController from './detect-tokens';
import { NetworkController } from './network';
import PreferencesController from './preferences';
function buildMessenger() {
return new ControllerMessenger().getRestricted({
name: 'DetectTokensController',
allowedEvents: ['NetworkController:stateChange'],
});
}
describe('DetectTokensController', function () {
let sandbox,
assetsContractController,
@ -230,23 +237,20 @@ describe('DetectTokensController', function () {
onPreferencesStateChange: preferences.store.subscribe.bind(
preferences.store,
),
onNetworkStateChange: (cb) =>
network.store.subscribe((networkState) => {
const modifiedNetworkState = {
...networkState,
providerConfig: {
...networkState.providerConfig,
},
};
return cb(modifiedNetworkState);
}),
onNetworkStateChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
'NetworkController:stateChange',
),
});
assetsContractController = new AssetsContractController({
onPreferencesStateChange: preferences.store.subscribe.bind(
preferences.store,
),
onNetworkStateChange: network.store.subscribe.bind(network.store),
onNetworkStateChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
'NetworkController:stateChange',
),
});
});
@ -257,7 +261,7 @@ describe('DetectTokensController', function () {
it('should poll on correct interval', async function () {
const stub = sinon.stub(global, 'setInterval');
new DetectTokensController({ interval: 1337 }); // eslint-disable-line no-new
new DetectTokensController({ messenger: buildMessenger(), interval: 1337 }); // eslint-disable-line no-new
assert.strictEqual(stub.getCall(0).args[1], 1337);
stub.restore();
});
@ -266,6 +270,7 @@ describe('DetectTokensController', function () {
const clock = sandbox.useFakeTimers();
await network.setProviderType(NETWORK_TYPES.MAINNET);
const controller = new DetectTokensController({
messenger: buildMessenger(),
preferences,
network,
keyringMemStore,
@ -302,6 +307,7 @@ describe('DetectTokensController', function () {
});
await tokenListController.start();
const controller = new DetectTokensController({
messenger: buildMessenger(),
preferences,
network,
keyringMemStore,
@ -325,6 +331,7 @@ describe('DetectTokensController', function () {
sandbox.useFakeTimers();
await network.setProviderType(NETWORK_TYPES.MAINNET);
const controller = new DetectTokensController({
messenger: buildMessenger(),
preferences,
network,
keyringMemStore,
@ -376,6 +383,7 @@ describe('DetectTokensController', function () {
sandbox.useFakeTimers();
await network.setProviderType(NETWORK_TYPES.MAINNET);
const controller = new DetectTokensController({
messenger: buildMessenger(),
preferences,
network,
keyringMemStore,
@ -434,6 +442,7 @@ describe('DetectTokensController', function () {
it('should trigger detect new tokens when change address', async function () {
sandbox.useFakeTimers();
const controller = new DetectTokensController({
messenger: buildMessenger(),
preferences,
network,
keyringMemStore,
@ -453,6 +462,7 @@ describe('DetectTokensController', function () {
it('should trigger detect new tokens when submit password', async function () {
sandbox.useFakeTimers();
const controller = new DetectTokensController({
messenger: buildMessenger(),
preferences,
network,
keyringMemStore,
@ -471,6 +481,7 @@ describe('DetectTokensController', function () {
const clock = sandbox.useFakeTimers();
await network.setProviderType(NETWORK_TYPES.MAINNET);
const controller = new DetectTokensController({
messenger: buildMessenger(),
preferences,
network,
keyringMemStore,
@ -492,6 +503,7 @@ describe('DetectTokensController', function () {
const clock = sandbox.useFakeTimers();
await network.setProviderType(NETWORK_TYPES.MAINNET);
const controller = new DetectTokensController({
messenger: buildMessenger(),
preferences,
network,
keyringMemStore,

View File

@ -435,7 +435,10 @@ export default class MetaMetricsController {
this.clearEventsAfterMetricsOptIn();
}
///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask)
this.updateExtensionUninstallUrl(participateInMetaMetrics, metaMetricsId);
///: END:ONLY_INCLUDE_IN
return metaMetricsId;
}

View File

@ -9,11 +9,7 @@ import {
MetaMetricsUserTrait,
} from '../../../shared/constants/metametrics';
import waitUntilCalled from '../../../test/lib/wait-until-called';
import {
CHAIN_IDS,
CURRENCY_SYMBOLS,
NETWORK_TYPES,
} from '../../../shared/constants/network';
import { CHAIN_IDS, CURRENCY_SYMBOLS } from '../../../shared/constants/network';
import * as Utils from '../lib/util';
import MetaMetricsController from './metametrics';
@ -77,28 +73,6 @@ const DEFAULT_PAGE_PROPERTIES = {
...DEFAULT_SHARED_PROPERTIES,
};
function getMockNetworkController() {
let state = {
providerConfig: {
type: NETWORK_TYPES.GOERLI,
chainId: FAKE_CHAIN_ID,
},
network: 'loading',
};
const onNetworkDidChange = sinon.stub();
const updateState = (newState) => {
state = { ...state, ...newState };
onNetworkDidChange.getCall(0).args[0]();
};
return {
store: {
getState: () => state,
updateState,
},
onNetworkDidChange,
};
}
function getMockPreferencesStore({ currentLocale = LOCALE } = {}) {
let preferencesStore = {
currentLocale,
@ -142,15 +116,16 @@ function getMetaMetricsController({
participateInMetaMetrics = true,
metaMetricsId = TEST_META_METRICS_ID,
preferencesStore = getMockPreferencesStore(),
networkController = getMockNetworkController(),
getCurrentChainId = () => FAKE_CHAIN_ID,
onNetworkDidChange = () => {
// do nothing
},
segmentInstance,
} = {}) {
return new MetaMetricsController({
segment: segmentInstance || segment,
getCurrentChainId: () =>
networkController.store.getState().providerConfig.chainId,
onNetworkDidChange:
networkController.onNetworkDidChange.bind(networkController),
getCurrentChainId,
onNetworkDidChange,
preferencesStore,
version: '0.0.1',
environment: 'test',
@ -166,6 +141,7 @@ function getMetaMetricsController({
extension: MOCK_EXTENSION,
});
}
describe('MetaMetricsController', function () {
const now = new Date();
let clock;
@ -213,17 +189,20 @@ describe('MetaMetricsController', function () {
});
it('should update when network changes', function () {
const networkController = getMockNetworkController();
let chainId = '0x111';
let networkDidChangeListener;
const onNetworkDidChange = (listener) => {
networkDidChangeListener = listener;
};
const metaMetricsController = getMetaMetricsController({
networkController,
getCurrentChainId: () => chainId,
onNetworkDidChange,
});
networkController.store.updateState({
providerConfig: {
type: 'NEW_NETWORK',
chainId: '0xaab',
},
});
assert.strictEqual(metaMetricsController.chainId, '0xaab');
chainId = '0x222';
networkDidChangeListener();
assert.strictEqual(metaMetricsController.chainId, '0x222');
});
it('should update when preferences changes', function () {

View File

@ -1,6 +1,7 @@
import EventEmitter from 'events';
import log from 'loglevel';
import { captureException } from '@sentry/browser';
import { isEqual } from 'lodash';
import {
PersonalMessageManager,
TypedMessageManager,
@ -14,11 +15,17 @@ import {
REFRESH_TOKEN_CHANGE_EVENT,
INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT,
} from '@metamask-institutional/sdk';
import {
handleMmiPortfolio,
setDashboardCookie,
} from '@metamask-institutional/portfolio-dashboard';
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils';
import { CHAIN_IDS } from '../../../shared/constants/network';
import {
BUILD_QUOTE_ROUTE,
CONNECT_HARDWARE_ROUTE,
} from '../../../ui/helpers/constants/routes';
import { previousValueComparator } from '../lib/util';
import { getPermissionBackgroundApiMethods } from './permissions';
export default class MMIController extends EventEmitter {
@ -69,6 +76,17 @@ export default class MMIController extends EventEmitter {
this.transactionUpdateController.subscribeToEvents();
});
}
this.preferencesController.store.subscribe(
previousValueComparator(async (prevState, currState) => {
const { identities: prevIdentities } = prevState;
const { identities: currIdentities } = currState;
if (isEqual(prevIdentities, currIdentities)) {
return;
}
await this.prepareMmiPortfolio();
}, this.preferencesController.store.getState()),
);
} // End of constructor
async persistKeyringsAfterRefreshTokenChange() {
@ -542,6 +560,44 @@ export default class MMIController extends EventEmitter {
});
}
async handleMmiDashboardData() {
await this.appStateController.getUnlockPromise(true);
const keyringAccounts = await this.keyringController.getAccounts();
const { identities } = this.preferencesController.store.getState();
const { metaMetricsId } = this.metaMetricsController.store.getState();
const getAccountDetails = (address) =>
this.custodyController.getAccountDetails(address);
const extensionId = this.extension.runtime.id;
const networks = [
...this.preferencesController.getRpcMethodPreferences(),
{ chainId: CHAIN_IDS.MAINNET },
{ chainId: CHAIN_IDS.GOERLI },
];
return handleMmiPortfolio({
keyringAccounts,
identities,
metaMetricsId,
networks,
getAccountDetails,
extensionId,
});
}
async prepareMmiPortfolio() {
if (!process.env.IN_TEST) {
try {
const mmiDashboardData = await this.handleMmiDashboardData();
const cookieSetUrls =
this.mmiConfigurationController.store.mmiConfiguration?.portfolio
?.cookieSetUrls;
setDashboardCookie(mmiDashboardData, cookieSetUrls);
} catch (error) {
console.error(error);
}
}
}
async setAccountAndNetwork(origin, address, chainId) {
await this.appStateController.getUnlockPromise(true);
const selectedAddress = this.preferencesController.getSelectedAddress();
@ -549,22 +605,25 @@ export default class MMIController extends EventEmitter {
this.preferencesController.setSelectedAddress(address);
}
const selectedChainId = parseInt(
this.networkController.getCurrentChainId(),
this.networkController.state.providerConfig.chainId,
16,
);
if (selectedChainId !== chainId && chainId === 1) {
this.networkController.setProviderType('mainnet');
await this.networkController.setProviderType('mainnet');
} else if (selectedChainId !== chainId) {
const network = this.preferencesController
.getFrequentRpcListDetail()
.find((item) => parseInt(item.chainId, 16) === chainId);
this.networkController.setRpcTarget(
network.rpcUrl,
network.chainId,
network.ticker,
network.nickname,
);
const foundNetworkConfiguration = Object.values(
this.networkController.state.networkConfigurations,
).find((networkConfiguration) => {
return parseInt(networkConfiguration.chainId, 16) === chainId;
});
if (foundNetworkConfiguration !== undefined) {
await this.networkConfiguration.setActiveNetwork(
foundNetworkConfiguration.id,
);
}
}
getPermissionBackgroundApiMethods(
this.permissionController,
).addPermittedAccount(origin, address);

View File

@ -1,7 +0,0 @@
import { NetworkClientType } from './create-network-client';
import { testsForProviderType } from './provider-api-tests/shared-tests';
describe('createNetworkClient', () => {
testsForProviderType(NetworkClientType.Infura);
testsForProviderType(NetworkClientType.Custom);
});

View File

@ -1,191 +0,0 @@
import {
createAsyncMiddleware,
createScaffoldMiddleware,
JsonRpcEngine,
mergeMiddleware,
JsonRpcMiddleware,
} from 'json-rpc-engine';
import {
createBlockCacheMiddleware,
createBlockRefMiddleware,
createBlockRefRewriteMiddleware,
createBlockTrackerInspectorMiddleware,
createInflightCacheMiddleware,
createFetchMiddleware,
createRetryOnEmptyMiddleware,
} from '@metamask/eth-json-rpc-middleware';
import {
providerFromEngine,
providerFromMiddleware,
SafeEventEmitterProvider,
} from '@metamask/eth-json-rpc-provider';
import { createInfuraMiddleware } from '@metamask/eth-json-rpc-infura';
import type { Hex } from '@metamask/utils/dist';
import { PollingBlockTracker } from 'eth-block-tracker/dist';
import { SECOND } from '../../../../shared/constants/time';
import {
BUILT_IN_INFURA_NETWORKS,
BuiltInInfuraNetwork,
} from '../../../../shared/constants/network';
export enum NetworkClientType {
Custom = 'custom',
Infura = 'infura',
}
type CustomNetworkConfiguration = {
chainId: Hex;
rpcUrl: string;
type: NetworkClientType.Custom;
};
type InfuraNetworkConfiguration = {
network: BuiltInInfuraNetwork;
infuraProjectId: string;
type: NetworkClientType.Infura;
};
/**
* Create a JSON RPC network client for a specific network.
*
* @param networkConfig - The network configuration.
* @returns
*/
export function createNetworkClient(
networkConfig: CustomNetworkConfiguration | InfuraNetworkConfiguration,
): { provider: SafeEventEmitterProvider; blockTracker: PollingBlockTracker } {
const rpcApiMiddleware =
networkConfig.type === NetworkClientType.Infura
? createInfuraMiddleware({
network: networkConfig.network,
projectId: networkConfig.infuraProjectId,
maxAttempts: 5,
source: 'metamask',
})
: createFetchMiddleware({
btoa: global.btoa,
fetch: global.fetch,
rpcUrl: networkConfig.rpcUrl,
});
const rpcProvider = providerFromMiddleware(rpcApiMiddleware);
const blockTrackerOpts =
process.env.IN_TEST && networkConfig.type === 'custom'
? { pollingInterval: SECOND }
: {};
const blockTracker = new PollingBlockTracker({
...blockTrackerOpts,
provider: rpcProvider,
});
const networkMiddleware =
networkConfig.type === NetworkClientType.Infura
? createInfuraNetworkMiddleware({
blockTracker,
network: networkConfig.network,
rpcProvider,
rpcApiMiddleware,
})
: createCustomNetworkMiddleware({
blockTracker,
chainId: networkConfig.chainId,
rpcApiMiddleware,
});
const engine = new JsonRpcEngine();
engine.push(networkMiddleware);
const provider = providerFromEngine(engine);
return { provider, blockTracker };
}
function createInfuraNetworkMiddleware({
blockTracker,
network,
rpcProvider,
rpcApiMiddleware,
}: {
blockTracker: PollingBlockTracker;
network: BuiltInInfuraNetwork;
rpcProvider: SafeEventEmitterProvider;
rpcApiMiddleware: JsonRpcMiddleware<unknown, unknown>;
}) {
return mergeMiddleware([
createNetworkAndChainIdMiddleware({ network }),
createBlockCacheMiddleware({ blockTracker }),
createInflightCacheMiddleware(),
createBlockRefMiddleware({ blockTracker, provider: rpcProvider }),
createRetryOnEmptyMiddleware({ blockTracker, provider: rpcProvider }),
createBlockTrackerInspectorMiddleware({ blockTracker }),
rpcApiMiddleware,
]);
}
function createNetworkAndChainIdMiddleware({
network,
}: {
network: BuiltInInfuraNetwork;
}) {
if (!BUILT_IN_INFURA_NETWORKS[network]) {
throw new Error(`createInfuraClient - unknown network "${network}"`);
}
const { chainId, networkId } = BUILT_IN_INFURA_NETWORKS[network];
return createScaffoldMiddleware({
eth_chainId: chainId,
net_version: networkId,
});
}
const createChainIdMiddleware = (
chainId: string,
): JsonRpcMiddleware<unknown, unknown> => {
return (req, res, next, end) => {
if (req.method === 'eth_chainId') {
res.result = chainId;
return end();
}
return next();
};
};
function createCustomNetworkMiddleware({
blockTracker,
chainId,
rpcApiMiddleware,
}: {
blockTracker: PollingBlockTracker;
chainId: string;
rpcApiMiddleware: any;
}) {
const testMiddlewares = process.env.IN_TEST
? [createEstimateGasDelayTestMiddleware()]
: [];
return mergeMiddleware([
...testMiddlewares,
createChainIdMiddleware(chainId),
createBlockRefRewriteMiddleware({ blockTracker }),
createBlockCacheMiddleware({ blockTracker }),
createInflightCacheMiddleware(),
createBlockTrackerInspectorMiddleware({ blockTracker }),
rpcApiMiddleware,
]);
}
/**
* For use in tests only.
* Adds a delay to `eth_estimateGas` calls.
*/
function createEstimateGasDelayTestMiddleware() {
return createAsyncMiddleware(async (req, _, next) => {
if (req.method === 'eth_estimateGas') {
await new Promise((resolve) => setTimeout(resolve, SECOND * 2));
}
return next();
});
}

View File

@ -1 +0,0 @@
export * from './network-controller';

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,284 +0,0 @@
/* eslint-disable jest/require-top-level-describe, jest/no-export */
import {
ProviderType,
withMockedCommunications,
withNetworkClient,
} from './helpers';
type TestsForRpcMethodThatCheckForBlockHashInResponseOptions = {
providerType: ProviderType;
numberOfParameters: number;
};
/**
* Defines tests which exercise the behavior exhibited by an RPC method that
* use `blockHash` in the response data to determine whether the response is
* cacheable.
*
* @param method - The name of the RPC method under test.
* @param additionalArgs - Additional arguments.
* @param additionalArgs.numberOfParameters - The number of parameters supported
* by the method under test.
* @param additionalArgs.providerType - The type of provider being tested;
* either `infura` or `custom` (default: "infura").
*/
export function testsForRpcMethodsThatCheckForBlockHashInResponse(
method: string,
{
numberOfParameters,
providerType,
}: TestsForRpcMethodThatCheckForBlockHashInResponseOptions,
) {
if (providerType !== 'infura' && providerType !== 'custom') {
throw new Error(
`providerType must be either "infura" or "custom", was "${providerType}" instead`,
);
}
it('does not hit the RPC endpoint more than once for identical requests and it has a valid blockHash', async () => {
const requests = [{ method }, { method }];
const mockResult = { blockHash: '0x1' };
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResult },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual([mockResult, mockResult]);
});
});
it('hits the RPC endpoint and does not reuse the result of a previous request if the latest block number was updated since', async () => {
const requests = [{ method }, { method }];
const mockResults = [{ blockHash: '0x100' }, { blockHash: '0x200' }];
await withMockedCommunications({ providerType }, async (comms) => {
// Note that we have to mock these requests in a specific order. The
// first block tracker request occurs because of the first RPC
// request. The second block tracker request, however, does not occur
// because of the second RPC request, but rather because we call
// `clock.runAll()` below.
comms.mockNextBlockTrackerRequest({ blockNumber: '0x1' });
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockNextBlockTrackerRequest({ blockNumber: '0x2' });
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
async (client) => {
const firstResult = await client.makeRpcCall(requests[0]);
// Proceed to the next iteration of the block tracker so that a new
// block is fetched and the current block is updated.
client.clock.runAll();
const secondResult = await client.makeRpcCall(requests[1]);
return [firstResult, secondResult];
},
);
expect(results).toStrictEqual(mockResults);
});
});
it('does not reuse the result of a previous request if result.blockHash was null', async () => {
const requests = [{ method }, { method }];
const mockResults = [
{ blockHash: null, extra: 'some value' },
{ blockHash: '0x100', extra: 'some other value' },
];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual(mockResults);
});
});
it('does not reuse the result of a previous request if result.blockHash was undefined', async () => {
const requests = [{ method }, { method }];
const mockResults = [
{ extra: 'some value' },
{ blockHash: '0x100', extra: 'some other value' },
];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual(mockResults);
});
});
it('does not reuse the result of a previous request if result.blockHash was "0x0000000000000000000000000000000000000000000000000000000000000000"', async () => {
const requests = [{ method }, { method }];
const mockResults = [
{
blockHash:
'0x0000000000000000000000000000000000000000000000000000000000000000',
extra: 'some value',
},
{ blockHash: '0x100', extra: 'some other value' },
];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual(mockResults);
});
});
for (const emptyValue of [null, undefined, '\u003cnil\u003e']) {
it(`does not retry an empty response of "${emptyValue}"`, async () => {
const request = { method };
const mockResult = emptyValue;
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: { result: mockResult },
});
const result = await withNetworkClient(
{ providerType },
({ makeRpcCall }) => makeRpcCall(request),
);
expect(result).toStrictEqual(mockResult);
});
});
it(`does not reuse the result of a previous request if it was "${emptyValue}"`, async () => {
const requests = [{ method }, { method }];
const mockResults = [emptyValue, { blockHash: '0x100' }];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual(mockResults);
});
});
}
for (const paramIndex of [...Array(numberOfParameters).keys()]) {
it(`does not reuse the result of a previous request with a valid blockHash if parameter at index "${paramIndex}" differs`, async () => {
const firstMockParams = [
...new Array(numberOfParameters).fill('some value'),
];
const secondMockParams = firstMockParams.slice();
secondMockParams[paramIndex] = 'another value';
const requests = [
{
method,
params: firstMockParams,
},
{ method, params: secondMockParams },
];
const mockResults = [{ blockHash: '0x100' }, { blockHash: '0x200' }];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual([mockResults[0], mockResults[1]]);
});
});
}
}

View File

@ -1,543 +0,0 @@
import nock, { Scope as NockScope } from 'nock';
import sinon from 'sinon';
import type { JSONRPCResponse } from '@json-rpc-specification/meta-schema';
import EthQuery from 'eth-query';
import { Hex } from '@metamask/utils';
import { BuiltInInfuraNetwork } from '../../../../../shared/constants/network';
import {
createNetworkClient,
NetworkClientType,
} from '../create-network-client';
/**
* A dummy value for the `infuraProjectId` option that `createInfuraClient`
* needs. (Infura should not be hit during tests, but just in case, this should
* not refer to a real project ID.)
*/
const MOCK_INFURA_PROJECT_ID = 'abc123';
/**
* A dummy value for the `rpcUrl` option that `createJsonRpcClient` needs. (This
* should not be hit during tests, but just in case, this should also not refer
* to a real Infura URL.)
*/
const MOCK_RPC_URL = 'http://foo.com';
/**
* A default value for the `eth_blockNumber` request that the block tracker
* makes.
*/
const DEFAULT_LATEST_BLOCK_NUMBER = '0x42';
/**
* A reference to the original `setTimeout` function so that we can use it even
* when using fake timers.
*/
const originalSetTimeout = setTimeout;
/**
* If you're having trouble writing a test and you're wondering why the test
* keeps failing, you can set `process.env.DEBUG_PROVIDER_TESTS` to `1`. This
* will turn on some extra logging.
*
* @param args - The arguments that `console.log` takes.
*/
function debug(...args: any) {
if (process.env.DEBUG_PROVIDER_TESTS === '1') {
console.log(...args);
}
}
/**
* Builds a Nock scope object for mocking provider requests.
*
* @param rpcUrl - The URL of the RPC endpoint.
* @returns The nock scope.
*/
function buildScopeForMockingRequests(rpcUrl: string): NockScope {
return nock(rpcUrl).filteringRequestBody((body) => {
debug('Nock Received Request: ', body);
return body;
});
}
type Request = { method: string; params?: any[] };
type Response = {
id?: number | string;
jsonrpc?: '2.0';
error?: any;
result?: any;
httpStatus?: number;
};
type ResponseBody = { body: JSONRPCResponse };
type BodyOrResponse = ResponseBody | Response;
type CurriedMockRpcCallOptions = {
request: Request;
// The response data.
response?: BodyOrResponse;
/**
* An error to throw while making the request.
* Takes precedence over `response`.
*/
error?: Error | string;
/**
* The amount of time that should pass before the
* request resolves with the response.
*/
delay?: number;
/**
* The number of times that the request is
* expected to be made.
*/
times?: number;
};
type MockRpcCallOptions = {
// A nock scope (a set of mocked requests scoped to a certain base URL).
nockScope: nock.Scope;
} & CurriedMockRpcCallOptions;
type MockRpcCallResult = nock.Interceptor | nock.Scope;
/**
* Mocks a JSON-RPC request sent to the provider with the given response.
* Provider type is inferred from the base url set on the nockScope.
*
* @param args - The arguments.
* @param args.nockScope - A nock scope (a set of mocked requests scoped to a
* certain base URL).
* @param args.request - The request data.
* @param args.response - Information concerning the response that the request
* should have. If a `body` property is present, this is taken as the complete
* response body. If an `httpStatus` property is present, then it is taken as
* the HTTP status code to respond with. Properties other than these two are
* used to build a complete response body (including `id` and `jsonrpc`
* properties).
* @param args.error - An error to throw while making the request. Takes
* precedence over `response`.
* @param args.delay - The amount of time that should pass before the request
* resolves with the response.
* @param args.times - The number of times that the request is expected to be
* made.
* @returns The nock scope.
*/
function mockRpcCall({
nockScope,
request,
response,
error,
delay,
times,
}: MockRpcCallOptions): MockRpcCallResult {
// eth-query always passes `params`, so even if we don't supply this property,
// for consistency with makeRpcCall, assume that the `body` contains it
const { method, params = [], ...rest } = request;
let httpStatus = 200;
let completeResponse: JSONRPCResponse = { id: 2, jsonrpc: '2.0' };
if (response !== undefined) {
if ('body' in response) {
completeResponse = response.body;
} else {
if (response.error) {
completeResponse.error = response.error;
} else {
completeResponse.result = response.result;
}
if (response.httpStatus) {
httpStatus = response.httpStatus;
}
}
}
/* @ts-expect-error The types for Nock do not include `basePath` in the interface for Nock.Scope. */
const url = new URL(nockScope.basePath).hostname.match(/(\.|^)infura.io$/u)
? `/v3/${MOCK_INFURA_PROJECT_ID}`
: '/';
debug('Mocking request:', {
url,
method,
params,
response,
error,
...rest,
times,
});
let nockRequest = nockScope.post(url, {
id: /\d*/u,
jsonrpc: '2.0',
method,
params,
...rest,
});
if (delay !== undefined) {
nockRequest = nockRequest.delay(delay);
}
if (times !== undefined) {
nockRequest = nockRequest.times(times);
}
if (error !== undefined) {
return nockRequest.replyWithError(error);
} else if (completeResponse !== undefined) {
return nockRequest.reply(httpStatus, (_, requestBody: any) => {
if (response !== undefined && !('body' in response)) {
if (response.id === undefined) {
completeResponse.id = requestBody.id;
} else {
completeResponse.id = response.id;
}
}
debug('Nock returning Response', completeResponse);
return completeResponse;
});
}
return nockRequest;
}
type MockBlockTrackerRequestOptions = {
/**
* A nock scope (a set of mocked requests scoped to a certain base url).
*/
nockScope: NockScope;
/**
* The block number that the block tracker should report, as a 0x-prefixed hex
* string.
*/
blockNumber: string;
};
/**
* Mocks the next request for the latest block that the block tracker will make.
*
* @param args - The arguments.
* @param args.nockScope - A nock scope (a set of mocked requests scoped to a
* certain base URL).
* @param args.blockNumber - The block number that the block tracker should
* report, as a 0x-prefixed hex string.
*/
function mockNextBlockTrackerRequest({
nockScope,
blockNumber = DEFAULT_LATEST_BLOCK_NUMBER,
}: MockBlockTrackerRequestOptions) {
mockRpcCall({
nockScope,
request: { method: 'eth_blockNumber', params: [] },
response: { result: blockNumber },
});
}
/**
* Mocks all requests for the latest block that the block tracker will make.
*
* @param args - The arguments.
* @param args.nockScope - A nock scope (a set of mocked requests scoped to a
* certain base URL).
* @param args.blockNumber - The block number that the block tracker should
* report, as a 0x-prefixed hex string.
*/
async function mockAllBlockTrackerRequests({
nockScope,
blockNumber = DEFAULT_LATEST_BLOCK_NUMBER,
}: MockBlockTrackerRequestOptions) {
const result = await mockRpcCall({
nockScope,
request: { method: 'eth_blockNumber', params: [] },
response: { result: blockNumber },
});
if ('persist' in result) {
result.persist();
}
}
/**
* Makes a JSON-RPC call through the given eth-query object.
*
* @param ethQuery - The eth-query object.
* @param request - The request data.
* @returns A promise that either resolves with the result from the JSON-RPC
* response if it is successful or rejects with the error from the JSON-RPC
* response otherwise.
*/
function makeRpcCall(ethQuery: EthQuery, request: Request) {
return new Promise((resolve, reject) => {
debug('[makeRpcCall] making request', request);
ethQuery.sendAsync(request, (error, result) => {
debug('[makeRpcCall > ethQuery handler] error', error, 'result', result);
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
export type ProviderType = 'infura' | 'custom';
export type MockOptions = {
infuraNetwork?: BuiltInInfuraNetwork;
providerType: ProviderType;
customRpcUrl?: string;
customChainId?: Hex;
};
export type MockCommunications = {
mockNextBlockTrackerRequest: (options?: any) => void;
mockAllBlockTrackerRequests: (options?: any) => void;
mockRpcCall: (options: CurriedMockRpcCallOptions) => MockRpcCallResult;
rpcUrl: string;
infuraNetwork: BuiltInInfuraNetwork;
};
/**
* Sets up request mocks for requests to the provider.
*
* @param options - An options bag.
* @param options.providerType - The type of network client being tested.
* @param options.infuraNetwork - The name of the Infura network being tested,
* assuming that `providerType` is "infura" (default: "mainnet").
* @param options.customRpcUrl - The URL of the custom RPC endpoint, assuming
* that `providerType` is "custom".
* @param fn - A function which will be called with an object that allows
* interaction with the network client.
* @returns The return value of the given function.
*/
export async function withMockedCommunications(
{
providerType,
infuraNetwork = 'mainnet',
customRpcUrl = MOCK_RPC_URL,
}: MockOptions,
fn: (comms: MockCommunications) => Promise<void>,
) {
if (providerType !== 'infura' && providerType !== 'custom') {
throw new Error(
`providerType must be either "infura" or "custom", was "${providerType}" instead`,
);
}
const rpcUrl =
providerType === 'infura'
? `https://${infuraNetwork}.infura.io`
: customRpcUrl;
const nockScope = buildScopeForMockingRequests(rpcUrl);
const curriedMockNextBlockTrackerRequest = (localOptions: any) =>
mockNextBlockTrackerRequest({ nockScope, ...localOptions });
const curriedMockAllBlockTrackerRequests = (localOptions: any) =>
mockAllBlockTrackerRequests({ nockScope, ...localOptions });
const curriedMockRpcCall = (localOptions: any) =>
mockRpcCall({ nockScope, ...localOptions });
const comms = {
mockNextBlockTrackerRequest: curriedMockNextBlockTrackerRequest,
mockAllBlockTrackerRequests: curriedMockAllBlockTrackerRequests,
mockRpcCall: curriedMockRpcCall,
rpcUrl,
infuraNetwork,
};
try {
return await fn(comms);
} finally {
nock.isDone();
nock.cleanAll();
}
}
type MockNetworkClient = {
blockTracker: any;
clock: sinon.SinonFakeTimers;
makeRpcCall: (request: Request) => Promise<any>;
makeRpcCallsInSeries: (requests: Request[]) => Promise<any[]>;
};
/**
* Some middleware contain logic which retries the request if some condition
* applies. This retrying always happens out of band via `setTimeout`, and
* because we are stubbing time via Jest's fake timers, we have to manually
* advance the clock so that the `setTimeout` handlers get fired. We don't know
* when these timers will get created, however, so we have to keep advancing
* timers until the request has been made an appropriate number of times.
* Unfortunately we don't have a good way to know how many times a request has
* been retried, but the good news is that the middleware won't end, and thus
* the promise which the RPC call returns won't get fulfilled, until all retries
* have been made.
*
* @param promise - The promise which is returned by the RPC call.
* @param clock - A Sinon clock object which can be used to advance to the next
* `setTimeout` handler.
*/
export async function waitForPromiseToBeFulfilledAfterRunningAllTimers(
promise: any,
clock: any,
) {
let hasPromiseBeenFulfilled = false;
let numTimesClockHasBeenAdvanced = 0;
promise
.catch((error: any) => {
// This is used to silence Node.js warnings about the rejection
// being handled asynchronously. The error is handled later when
// `promise` is awaited, but we log it here anyway in case it gets
// swallowed.
debug(error);
})
.finally(() => {
hasPromiseBeenFulfilled = true;
});
// `hasPromiseBeenFulfilled` is modified asynchronously.
/* eslint-disable-next-line no-unmodified-loop-condition */
while (!hasPromiseBeenFulfilled && numTimesClockHasBeenAdvanced < 15) {
clock.runAll();
await new Promise((resolve) => originalSetTimeout(resolve, 10));
numTimesClockHasBeenAdvanced += 1;
}
return promise;
}
/**
* Builds a provider from the middleware (for the provider type) along with a
* block tracker, runs the given function with those two things, and then
* ensures the block tracker is stopped at the end.
*
* @param options - An options bag.
* @param options.providerType - The type of network client being tested.
* @param options.infuraNetwork - The name of the Infura network being tested,
* assuming that `providerType` is "infura" (default: "mainnet").
* @param options.customRpcUrl - The URL of the custom RPC endpoint, assuming
* that `providerType` is "custom".
* @param options.customChainId - The chain id belonging to the custom RPC
* endpoint, assuming that `providerType` is "custom" (default: "0x1").
* @param fn - A function which will be called with an object that allows
* interaction with the network client.
* @returns The return value of the given function.
*/
export async function withNetworkClient(
{
providerType,
infuraNetwork = 'mainnet',
customRpcUrl = MOCK_RPC_URL,
customChainId = '0x1',
}: MockOptions,
fn: (client: MockNetworkClient) => Promise<any>,
) {
if (providerType !== 'infura' && providerType !== 'custom') {
throw new Error(
`providerType must be either "infura" or "custom", was "${providerType}" instead`,
);
}
// Faking timers ends up doing two things:
// 1. Halting the block tracker (which depends on `setTimeout` to periodically
// request the latest block) set up in `eth-json-rpc-middleware`
// 2. Halting the retry logic in `@metamask/eth-json-rpc-infura` (which also
// depends on `setTimeout`)
const clock = sinon.useFakeTimers();
// The JSON-RPC client wraps `eth_estimateGas` so that it takes 2 seconds longer
// than it usually would to complete. Or at least it should — this doesn't
// appear to be working correctly. Unset `IN_TEST` on `process.env` to prevent
// this behavior.
const inTest = process.env.IN_TEST;
delete process.env.IN_TEST;
const clientUnderTest =
providerType === 'infura'
? createNetworkClient({
network: infuraNetwork,
infuraProjectId: MOCK_INFURA_PROJECT_ID,
type: NetworkClientType.Infura,
})
: createNetworkClient({
chainId: customChainId,
rpcUrl: customRpcUrl,
type: NetworkClientType.Custom,
});
process.env.IN_TEST = inTest;
const { provider, blockTracker } = clientUnderTest;
const ethQuery = new EthQuery(provider);
const curriedMakeRpcCall = (request: Request) =>
makeRpcCall(ethQuery, request);
const makeRpcCallsInSeries = async (requests: Request[]) => {
const responses = [];
for (const request of requests) {
responses.push(await curriedMakeRpcCall(request));
}
return responses;
};
const client = {
blockTracker,
clock,
makeRpcCall: curriedMakeRpcCall,
makeRpcCallsInSeries,
};
try {
return await fn(client);
} finally {
await blockTracker.destroy();
clock.restore();
}
}
type BuildMockParamsOptions = {
// The block parameter value to set.
blockParam: any;
// The index of the block parameter.
blockParamIndex: number;
};
/**
* Build mock parameters for a JSON-RPC call.
*
* The string 'some value' is used as the default value for each entry. The
* block parameter index determines the number of parameters to generate.
*
* The block parameter can be set to a custom value. If no value is given, it
* is set as undefined.
*
* @param args - Arguments.
* @param args.blockParamIndex - The index of the block parameter.
* @param args.blockParam - The block parameter value to set.
* @returns The mock params.
*/
export function buildMockParams({
blockParam,
blockParamIndex,
}: BuildMockParamsOptions) {
const params = new Array(blockParamIndex).fill('some value');
params[blockParamIndex] = blockParam;
return params;
}
/**
* Returns a partial JSON-RPC request object, with the "block" param replaced
* with the given value.
*
* @param request - The request object.
* @param request.method - The request method.
* @param request.params - The request params.
* @param blockParamIndex - The index within the `params` array of the block
* param.
* @param blockParam - The desired block param value.
* @returns The updated request object.
*/
export function buildRequestWithReplacedBlockParam(
{ method, params = [] }: Request,
blockParamIndex: number,
blockParam: any,
) {
const updatedParams = params.slice();
updatedParams[blockParamIndex] = blockParam;
return { method, params: updatedParams };
}

View File

@ -1,977 +0,0 @@
/* eslint-disable jest/require-top-level-describe, jest/no-export */
import {
ProviderType,
waitForPromiseToBeFulfilledAfterRunningAllTimers,
withMockedCommunications,
withNetworkClient,
} from './helpers';
import {
buildFetchFailedErrorMessage,
buildInfuraClientRetriesExhaustedErrorMessage,
buildJsonRpcEngineEmptyResponseErrorMessage,
} from './shared-tests';
type TestsForRpcMethodAssumingNoBlockParamOptions = {
providerType: ProviderType;
numberOfParameters: number;
};
/**
* Defines tests which exercise the behavior exhibited by an RPC method which is
* assumed to not take a block parameter. Even if it does, the value of this
* parameter will not be used in determining how to cache the method.
*
* @param method - The name of the RPC method under test.
* @param additionalArgs - Additional arguments.
* @param additionalArgs.numberOfParameters - The number of parameters supported by the method under test.
* @param additionalArgs.providerType - The type of provider being tested;
* either `infura` or `custom` (default: "infura").
*/
export function testsForRpcMethodAssumingNoBlockParam(
method: string,
{
numberOfParameters,
providerType,
}: TestsForRpcMethodAssumingNoBlockParamOptions,
) {
if (providerType !== 'infura' && providerType !== 'custom') {
throw new Error(
`providerType must be either "infura" or "custom", was "${providerType}" instead`,
);
}
it('does not hit the RPC endpoint more than once for identical requests', async () => {
const requests = [{ method }, { method }];
const mockResults = ['first result', 'second result'];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual([mockResults[0], mockResults[0]]);
});
});
for (const paramIndex of [...Array(numberOfParameters).keys()]) {
it(`does not reuse the result of a previous request if parameter at index "${paramIndex}" differs`, async () => {
const firstMockParams = [
...new Array(numberOfParameters).fill('some value'),
];
const secondMockParams = firstMockParams.slice();
secondMockParams[paramIndex] = 'another value';
const requests = [
{
method,
params: firstMockParams,
},
{ method, params: secondMockParams },
];
const mockResults = ['some result', 'another result'];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual([mockResults[0], mockResults[1]]);
});
});
}
it('hits the RPC endpoint and does not reuse the result of a previous request if the latest block number was updated since', async () => {
const requests = [{ method }, { method }];
const mockResults = ['first result', 'second result'];
await withMockedCommunications({ providerType }, async (comms) => {
// Note that we have to mock these requests in a specific order. The
// first block tracker request occurs because of the first RPC request.
// The second block tracker request, however, does not occur because of
// the second RPC request, but rather because we call `clock.runAll()`
// below.
comms.mockNextBlockTrackerRequest({ blockNumber: '0x1' });
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockNextBlockTrackerRequest({ blockNumber: '0x2' });
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
async (client) => {
const firstResult = await client.makeRpcCall(requests[0]);
// Proceed to the next iteration of the block tracker so that a new
// block is fetched and the current block is updated.
client.clock.runAll();
const secondResult = await client.makeRpcCall(requests[1]);
return [firstResult, secondResult];
},
);
expect(results).toStrictEqual(mockResults);
});
});
for (const emptyValue of [null, undefined, '\u003cnil\u003e']) {
it(`does not retry an empty response of "${emptyValue}"`, async () => {
const request = { method };
const mockResult = emptyValue;
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: { result: mockResult },
});
const result = await withNetworkClient(
{ providerType },
({ makeRpcCall }) => makeRpcCall(request),
);
expect(result).toStrictEqual(mockResult);
});
});
it(`does not reuse the result of a previous request if it was "${emptyValue}"`, async () => {
const requests = [{ method }, { method }];
const mockResults = [emptyValue, 'some result'];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual(mockResults);
});
});
}
it('queues requests while a previous identical call is still pending, then runs the queue when it finishes, reusing the result from the first request', async () => {
const requests = [{ method }, { method }, { method }];
const mockResults = ['first result', 'second result', 'third result'];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
delay: 100,
});
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
comms.mockRpcCall({
request: requests[2],
response: { result: mockResults[2] },
});
const results = await withNetworkClient(
{ providerType },
async (client) => {
const resultPromises = [
client.makeRpcCall(requests[0]),
client.makeRpcCall(requests[1]),
client.makeRpcCall(requests[2]),
];
const firstResult = await resultPromises[0];
// The inflight cache middleware uses setTimeout to run the handlers,
// so run them now
client.clock.runAll();
const remainingResults = await Promise.all(resultPromises.slice(1));
return [firstResult, ...remainingResults];
},
);
expect(results).toStrictEqual([
mockResults[0],
mockResults[0],
mockResults[0],
]);
});
});
it('throws a custom error if the request to the RPC endpoint returns a 405 response', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: {
httpStatus: 405,
},
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
'The method does not exist / is not available',
);
});
});
// There is a difference in how we are testing the Infura middleware vs. the
// custom RPC middleware (or, more specifically, the fetch middleware) because
// of what both middleware treat as rate limiting errors. In this case, the
// fetch middleware treats a 418 response from the RPC endpoint as such an
// error, whereas to the Infura middleware, it is a 429 response.
if (providerType === 'infura') {
it('throws an undescriptive error if the request to the RPC endpoint returns a 418 response', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { id: 123, method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: {
httpStatus: 418,
},
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
'{"id":123,"jsonrpc":"2.0"}',
);
});
});
it('throws an error with a custom message if the request to the RPC endpoint returns a 429 response', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: {
httpStatus: 429,
},
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
'Request is being rate limited',
);
});
});
} else {
it('throws a custom error if the request to the RPC endpoint returns a 418 response', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: {
httpStatus: 418,
},
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
'Request is being rate limited.',
);
});
});
it('throws an undescriptive error if the request to the RPC endpoint returns a 429 response', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: {
httpStatus: 429,
},
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
"Non-200 status code: '429'",
);
});
});
}
it('throws a generic, undescriptive error if the request to the RPC endpoint returns a response that is not 405, 418, 429, 503, or 504', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: {
id: 12345,
jsonrpc: '2.0',
error: 'some error',
httpStatus: 420,
},
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall }) => makeRpcCall(request),
);
const errorMessage =
providerType === 'infura'
? '{"id":12345,"jsonrpc":"2.0","error":"some error"}'
: "Non-200 status code: '420'";
await expect(promiseForResult).rejects.toThrow(errorMessage);
});
});
[503, 504].forEach((httpStatus) => {
it(`retries the request to the RPC endpoint up to 5 times if it returns a ${httpStatus} response, returning the successful result if there is one on the 5th try`, async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
// Here we have the request fail for the first 4 tries, then succeed
// on the 5th try.
comms.mockRpcCall({
request,
response: {
error: 'Some error',
httpStatus,
},
times: 4,
});
comms.mockRpcCall({
request,
response: {
result: 'the result',
httpStatus: 200,
},
});
const result = await withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
expect(result).toStrictEqual('the result');
});
});
it(`causes a request to fail with a custom error if the request to the RPC endpoint returns a ${httpStatus} response 5 times in a row`, async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: {
error: 'Some error',
httpStatus,
},
times: 5,
});
comms.mockNextBlockTrackerRequest();
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
const err =
providerType === 'infura'
? buildInfuraClientRetriesExhaustedErrorMessage('Gateway timeout')
: buildJsonRpcEngineEmptyResponseErrorMessage(method);
await expect(promiseForResult).rejects.toThrow(err);
});
});
});
it('retries the request to the RPC endpoint up to 5 times if an "ETIMEDOUT" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
// Here we have the request fail for the first 4 tries, then succeed
// on the 5th try.
comms.mockRpcCall({
request,
error: 'ETIMEDOUT: Some message',
times: 4,
});
comms.mockRpcCall({
request,
response: {
result: 'the result',
httpStatus: 200,
},
});
const result = await withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
expect(result).toStrictEqual('the result');
});
});
// Both the Infura and fetch middleware detect ETIMEDOUT errors and will
// automatically retry the request to the RPC endpoint in question, but both
// produce a different error if the number of retries is exhausted.
if (providerType === 'infura') {
it('causes a request to fail with a custom error if an "ETIMEDOUT" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
const errorMessage = 'ETIMEDOUT: Some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
times: 5,
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
await expect(promiseForResult).rejects.toThrow(
buildInfuraClientRetriesExhaustedErrorMessage(errorMessage),
);
});
});
} else {
it('returns an empty response if an "ETIMEDOUT" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
const errorMessage = 'ETIMEDOUT: Some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
times: 5,
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
await expect(promiseForResult).rejects.toThrow(
buildJsonRpcEngineEmptyResponseErrorMessage(method),
);
});
});
}
// The Infura middleware treats a response that contains an ECONNRESET message
// as an innocuous error that is likely to disappear on a retry. The custom
// RPC middleware, on the other hand, does not specially handle this error.
if (providerType === 'infura') {
it('retries the request to the RPC endpoint up to 5 times if an "ECONNRESET" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
// Here we have the request fail for the first 4 tries, then succeed
// on the 5th try.
comms.mockRpcCall({
request,
error: 'ECONNRESET: Some message',
times: 4,
});
comms.mockRpcCall({
request,
response: {
result: 'the result',
httpStatus: 200,
},
});
const result = await withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
expect(result).toStrictEqual('the result');
});
});
it('causes a request to fail with a custom error if an "ECONNRESET" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
const errorMessage = 'ECONNRESET: Some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
times: 5,
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
await expect(promiseForResult).rejects.toThrow(
buildInfuraClientRetriesExhaustedErrorMessage(errorMessage),
);
});
});
} else {
it('does not retry the request to the RPC endpoint, but throws immediately, if an "ECONNRESET" error is thrown while making the request', async () => {
const customRpcUrl = 'http://example.com';
await withMockedCommunications(
{ providerType, customRpcUrl },
async (comms) => {
const request = { method };
const errorMessage = 'ECONNRESET: Some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
});
const promiseForResult = withNetworkClient(
{ providerType, customRpcUrl },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
buildFetchFailedErrorMessage(customRpcUrl, errorMessage),
);
},
);
});
}
// Both the Infura and fetch middleware will attempt to parse the response
// body as JSON, and if this step produces an error, both middleware will also
// attempt to retry the request. However, this error handling code is slightly
// different between the two. As the error in this case is a SyntaxError, the
// Infura middleware will catch it immediately, whereas the custom RPC
// middleware will catch it and re-throw a separate error, which it then
// catches later.
if (providerType === 'infura') {
it('retries the request to the RPC endpoint up to 5 times if an "SyntaxError" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
// Here we have the request fail for the first 4 tries, then succeed
// on the 5th try.
comms.mockRpcCall({
request,
error: 'SyntaxError: Some message',
times: 4,
});
comms.mockRpcCall({
request,
response: {
result: 'the result',
httpStatus: 200,
},
});
const result = await withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
expect(result).toStrictEqual('the result');
});
});
it('causes a request to fail with a custom error if an "SyntaxError" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
const errorMessage = 'SyntaxError: Some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
times: 5,
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
await expect(promiseForResult).rejects.toThrow(
buildInfuraClientRetriesExhaustedErrorMessage(errorMessage),
);
});
});
it('does not retry the request to the RPC endpoint, but throws immediately, if a "failed to parse response body" error is thrown while making the request', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
const errorMessage = 'failed to parse response body: some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
});
const promiseForResult = withNetworkClient(
{ providerType, infuraNetwork: comms.infuraNetwork },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
buildFetchFailedErrorMessage(comms.rpcUrl, errorMessage),
);
});
});
} else {
it('does not retry the request to the RPC endpoint, but throws immediately, if a "SyntaxError" error is thrown while making the request', async () => {
const customRpcUrl = 'http://example.com';
await withMockedCommunications(
{ providerType, customRpcUrl },
async (comms) => {
const request = { method };
const errorMessage = 'SyntaxError: Some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
});
const promiseForResult = withNetworkClient(
{ providerType, customRpcUrl },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
buildFetchFailedErrorMessage(customRpcUrl, errorMessage),
);
},
);
});
it('retries the request to the RPC endpoint up to 5 times if a "failed to parse response body" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
// Here we have the request fail for the first 4 tries, then succeed
// on the 5th try.
comms.mockRpcCall({
request,
error: 'failed to parse response body: some message',
times: 4,
});
comms.mockRpcCall({
request,
response: {
result: 'the result',
httpStatus: 200,
},
});
const result = await withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
expect(result).toStrictEqual('the result');
});
});
it('returns an empty response if a "failed to parse response body" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
const errorMessage = 'failed to parse response body: some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
times: 5,
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
await expect(promiseForResult).rejects.toThrow(
buildJsonRpcEngineEmptyResponseErrorMessage(method),
);
});
});
}
// Only the custom RPC middleware will detect a "Failed to fetch" error and
// attempt to retry the request to the RPC endpoint; the Infura middleware
// does not.
if (providerType === 'infura') {
it('does not retry the request to the RPC endpoint, but throws immediately, if a "Failed to fetch" error is thrown while making the request', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
const errorMessage = 'Failed to fetch: some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
});
const promiseForResult = withNetworkClient(
{ providerType, infuraNetwork: comms.infuraNetwork },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
buildFetchFailedErrorMessage(comms.rpcUrl, errorMessage),
);
});
});
} else {
it('retries the request to the RPC endpoint up to 5 times if a "Failed to fetch" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
// Here we have the request fail for the first 4 tries, then succeed
// on the 5th try.
comms.mockRpcCall({
request,
error: 'Failed to fetch: some message',
times: 4,
});
comms.mockRpcCall({
request,
response: {
result: 'the result',
httpStatus: 200,
},
});
const result = await withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
expect(result).toStrictEqual('the result');
});
});
it('returns an empty response if a "Failed to fetch" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
const errorMessage = 'Failed to fetch: some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
times: 5,
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
await expect(promiseForResult).rejects.toThrow(
buildJsonRpcEngineEmptyResponseErrorMessage(method),
);
});
});
}
}

View File

@ -1,63 +0,0 @@
/* eslint-disable jest/require-top-level-describe, jest/no-export */
import { fill } from 'lodash';
import {
ProviderType,
withMockedCommunications,
withNetworkClient,
} from './helpers';
type TestsForRpcMethodNotHandledByMiddlewareOptions = {
providerType: ProviderType;
numberOfParameters: number;
};
/**
* Defines tests which exercise the behavior exhibited by an RPC method that
* is not handled specially by the network client middleware.
*
* @param method - The name of the RPC method under test.
* @param additionalArgs - Additional arguments.
* @param additionalArgs.providerType - The type of provider being tested;
* either `infura` or `custom`.
* @param additionalArgs.numberOfParameters - The number of parameters that this
* RPC method takes.
*/
export function testsForRpcMethodNotHandledByMiddleware(
method: string,
{
providerType,
numberOfParameters,
}: TestsForRpcMethodNotHandledByMiddlewareOptions,
) {
if (providerType !== 'infura' && providerType !== 'custom') {
throw new Error(
`providerType must be either "infura" or "custom", was "${providerType}" instead`,
);
}
it('attempts to pass the request off to the RPC endpoint', async () => {
const request = {
method,
params: fill(Array(numberOfParameters), 'some value'),
};
const expectedResult = 'the result';
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: { result: expectedResult },
});
const actualResult = await withNetworkClient(
{ providerType },
({ makeRpcCall }) => makeRpcCall(request),
);
expect(actualResult).toStrictEqual(expectedResult);
});
});
}

View File

@ -1,372 +0,0 @@
/* eslint-disable jest/require-top-level-describe, jest/no-export, jest/no-identical-title */
import { testsForRpcMethodsThatCheckForBlockHashInResponse } from './block-hash-in-response';
import { testsForRpcMethodSupportingBlockParam } from './block-param';
import {
ProviderType,
withMockedCommunications,
withNetworkClient,
} from './helpers';
import { testsForRpcMethodAssumingNoBlockParam } from './no-block-param';
import { testsForRpcMethodNotHandledByMiddleware } from './not-handled-by-middleware';
/**
* Constructs an error message that the Infura client would produce in the event
* that it has attempted to retry the request to Infura and has failed.
*
* @param reason - The exact reason for failure.
* @returns The error message.
*/
export function buildInfuraClientRetriesExhaustedErrorMessage(reason: string) {
return new RegExp(
`^InfuraProvider - cannot complete request. All retries exhausted\\..+${reason}`,
'us',
);
}
/**
* Constructs an error message that JsonRpcEngine would produce in the event
* that the response object is empty as it leaves the middleware.
*
* @param method - The RPC method.
* @returns The error message.
*/
export function buildJsonRpcEngineEmptyResponseErrorMessage(method: string) {
return new RegExp(
`^JsonRpcEngine: Response has no error or result for request:.+"method": "${method}"`,
'us',
);
}
/**
* Constructs an error message that `fetch` with throw if it cannot make a
* request.
*
* @param url - The URL being fetched
* @param reason - The reason.
* @returns The error message.
*/
export function buildFetchFailedErrorMessage(url: string, reason: string) {
return new RegExp(
`^request to ${url}(/[^/ ]*)+ failed, reason: ${reason}`,
'us',
);
}
/**
* Defines tests that are common to both the Infura and JSON-RPC network client.
*
* @param providerType - The type of provider being tested, which determines
* which suite of middleware is being tested. If `infura`, then the middleware
* exposed by `createInfuraClient` is tested; if `custom`, then the middleware
* exposed by `createJsonRpcClient` will be tested.
*/
export function testsForProviderType(providerType: ProviderType) {
// Ethereum JSON-RPC spec: <https://ethereum.github.io/execution-apis/api-documentation/>
// Infura documentation: <https://docs.infura.io/infura/networks/ethereum/json-rpc-methods>
describe('methods included in the Ethereum JSON-RPC spec', () => {
describe('methods not handled by middleware', () => {
const notHandledByMiddleware = [
// This list is presented in the same order as in the network client
// tests on the core side.
{ name: 'eth_newFilter', numberOfParameters: 1 },
{ name: 'eth_getFilterChanges', numberOfParameters: 1 },
{ name: 'eth_newBlockFilter', numberOfParameters: 0 },
{ name: 'eth_newPendingTransactionFilter', numberOfParameters: 0 },
{ name: 'eth_uninstallFilter', numberOfParameters: 1 },
{ name: 'eth_sendRawTransaction', numberOfParameters: 1 },
{ name: 'eth_sendTransaction', numberOfParameters: 1 },
{ name: 'eth_sign', numberOfParameters: 2 },
{ name: 'eth_createAccessList', numberOfParameters: 2 },
{ name: 'eth_getLogs', numberOfParameters: 1 },
{ name: 'eth_getProof', numberOfParameters: 3 },
{ name: 'eth_getWork', numberOfParameters: 0 },
{ name: 'eth_maxPriorityFeePerGas', numberOfParameters: 0 },
{ name: 'eth_submitHashRate', numberOfParameters: 2 },
{ name: 'eth_submitWork', numberOfParameters: 3 },
{ name: 'eth_syncing', numberOfParameters: 0 },
{ name: 'eth_feeHistory', numberOfParameters: 3 },
{ name: 'debug_getRawHeader', numberOfParameters: 1 },
{ name: 'debug_getRawBlock', numberOfParameters: 1 },
{ name: 'debug_getRawTransaction', numberOfParameters: 1 },
{ name: 'debug_getRawReceipts', numberOfParameters: 1 },
{ name: 'debug_getBadBlocks', numberOfParameters: 0 },
{ name: 'eth_accounts', numberOfParameters: 0 },
{ name: 'eth_coinbase', numberOfParameters: 0 },
{ name: 'eth_hashrate', numberOfParameters: 0 },
{ name: 'eth_mining', numberOfParameters: 0 },
{ name: 'eth_signTransaction', numberOfParameters: 1 },
];
notHandledByMiddleware.forEach(({ name, numberOfParameters }) => {
describe(`method name: ${name}`, () => {
testsForRpcMethodNotHandledByMiddleware(name, {
providerType,
numberOfParameters,
});
});
});
});
describe('methods with block hashes in their result', () => {
const methodsWithBlockHashInResponse = [
{ name: 'eth_getTransactionByHash', numberOfParameters: 1 },
{ name: 'eth_getTransactionReceipt', numberOfParameters: 1 },
];
methodsWithBlockHashInResponse.forEach(({ name, numberOfParameters }) => {
describe(`method name: ${name}`, () => {
testsForRpcMethodsThatCheckForBlockHashInResponse(name, {
numberOfParameters,
providerType,
});
});
});
});
describe('methods that assume there is no block param', () => {
const assumingNoBlockParam = [
{ name: 'eth_getFilterLogs', numberOfParameters: 1 },
{ name: 'eth_blockNumber', numberOfParameters: 0 },
{ name: 'eth_estimateGas', numberOfParameters: 2 },
{ name: 'eth_gasPrice', numberOfParameters: 0 },
{ name: 'eth_getBlockByHash', numberOfParameters: 2 },
{
name: 'eth_getBlockTransactionCountByHash',
numberOfParameters: 1,
},
{
name: 'eth_getTransactionByBlockHashAndIndex',
numberOfParameters: 2,
},
{ name: 'eth_getUncleByBlockHashAndIndex', numberOfParameters: 2 },
{ name: 'eth_getUncleCountByBlockHash', numberOfParameters: 1 },
];
const blockParamIgnored = [
{ name: 'eth_getUncleCountByBlockNumber', numberOfParameters: 1 },
{ name: 'eth_getUncleByBlockNumberAndIndex', numberOfParameters: 2 },
{
name: 'eth_getTransactionByBlockNumberAndIndex',
numberOfParameters: 2,
},
{
name: 'eth_getBlockTransactionCountByNumber',
numberOfParameters: 1,
},
];
assumingNoBlockParam
.concat(blockParamIgnored)
.forEach(({ name, numberOfParameters }) =>
describe(`method name: ${name}`, () => {
testsForRpcMethodAssumingNoBlockParam(name, {
providerType,
numberOfParameters,
});
}),
);
});
describe('methods that have a param to specify the block', () => {
const supportingBlockParam = [
{
name: 'eth_call',
blockParamIndex: 1,
numberOfParameters: 2,
},
{
name: 'eth_getBalance',
blockParamIndex: 1,
numberOfParameters: 2,
},
{
name: 'eth_getBlockByNumber',
blockParamIndex: 0,
numberOfParameters: 2,
},
{ name: 'eth_getCode', blockParamIndex: 1, numberOfParameters: 2 },
{
name: 'eth_getStorageAt',
blockParamIndex: 2,
numberOfParameters: 3,
},
{
name: 'eth_getTransactionCount',
blockParamIndex: 1,
numberOfParameters: 2,
},
];
supportingBlockParam.forEach(
({ name, blockParamIndex, numberOfParameters }) => {
describe(`method name: ${name}`, () => {
testsForRpcMethodSupportingBlockParam(name, {
providerType,
blockParamIndex,
numberOfParameters,
});
});
},
);
});
describe('other methods', () => {
describe('eth_getTransactionByHash', () => {
it("refreshes the block tracker's current block if it is less than the block number that comes back in the response", async () => {
const method = 'eth_getTransactionByHash';
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' });
// This is our request.
comms.mockRpcCall({
request,
response: {
result: {
blockNumber: '0x200',
},
},
});
comms.mockNextBlockTrackerRequest({ blockNumber: '0x300' });
await withNetworkClient(
{ providerType },
async ({ makeRpcCall, blockTracker }) => {
await makeRpcCall(request);
expect(blockTracker.getCurrentBlock()).toStrictEqual('0x300');
},
);
});
});
});
describe('eth_getTransactionReceipt', () => {
it("refreshes the block tracker's current block if it is less than the block number that comes back in the response", async () => {
const method = 'eth_getTransactionReceipt';
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' });
// This is our request.
comms.mockRpcCall({
request,
response: {
result: {
blockNumber: '0x200',
},
},
});
comms.mockNextBlockTrackerRequest({ blockNumber: '0x300' });
await withNetworkClient(
{ providerType },
async ({ makeRpcCall, blockTracker }) => {
await makeRpcCall(request);
expect(blockTracker.getCurrentBlock()).toStrictEqual('0x300');
},
);
});
});
});
describe('eth_chainId', () => {
it('does not hit the RPC endpoint, instead returning the configured chain id', async () => {
const networkId = await withNetworkClient(
{ providerType: 'custom', customChainId: '0x1' },
({ makeRpcCall }) => {
return makeRpcCall({ method: 'eth_chainId' });
},
);
expect(networkId).toStrictEqual('0x1');
});
});
});
});
describe('methods not included in the Ethereum JSON-RPC spec', () => {
describe('methods not handled by middleware', () => {
const notHandledByMiddleware = [
// This list is presented in the same order as in the network client
// tests on the core side.
{ name: 'net_listening', numberOfParameters: 0 },
{ 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 },
];
notHandledByMiddleware.forEach(({ name, numberOfParameters }) => {
describe(`method name: ${name}`, () => {
testsForRpcMethodNotHandledByMiddleware(name, {
providerType,
numberOfParameters,
});
});
});
});
describe('methods that assume there is no block param', () => {
const assumingNoBlockParam = [
{ name: 'web3_clientVersion', numberOfParameters: 0 },
{ name: 'eth_protocolVersion', numberOfParameters: 0 },
];
assumingNoBlockParam.forEach(({ name, numberOfParameters }) =>
describe(`method name: ${name}`, () => {
testsForRpcMethodAssumingNoBlockParam(name, {
providerType,
numberOfParameters,
});
}),
);
});
describe('other methods', () => {
describe('net_version', () => {
// The Infura middleware includes `net_version` in its scaffold
// middleware, whereas the custom RPC middleware does not.
if (providerType === 'infura') {
it('does not hit Infura, instead returning the network ID that maps to the Infura network, as a decimal string', async () => {
const networkId = await withNetworkClient(
{ providerType: 'infura', infuraNetwork: 'goerli' },
({ makeRpcCall }) => {
return makeRpcCall({
method: 'net_version',
});
},
);
expect(networkId).toStrictEqual('5');
});
} else {
it('hits the RPC endpoint', async () => {
await withMockedCommunications(
{ providerType: 'custom' },
async (comms) => {
comms.mockRpcCall({
request: { method: 'net_version' },
response: { result: '1' },
});
const networkId = await withNetworkClient(
{ providerType: 'custom' },
({ makeRpcCall }) => {
return makeRpcCall({
method: 'net_version',
});
},
);
expect(networkId).toStrictEqual('1');
},
);
});
}
});
});
});
}

View File

@ -1,11 +0,0 @@
import { PollingBlockTracker } from 'eth-block-tracker';
/**
* Acts like a PollingBlockTracker, but doesn't start the polling loop or
* make any requests.
*/
export class FakeBlockTracker extends PollingBlockTracker {
async _start() {
// do nothing
}
}

View File

@ -1,206 +0,0 @@
import { inspect, isDeepStrictEqual } from 'util';
import {
JsonRpcEngine,
JsonRpcRequest,
JsonRpcResponse,
} from 'json-rpc-engine';
import { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider/dist/safe-event-emitter-provider';
// Store this in case it gets stubbed later
const originalSetTimeout = global.setTimeout;
/**
* An object that allows specifying the behavior of a specific invocation of
* `sendAsync`. The `method` always identifies the stub, but the behavior
* may be specified multiple ways: `sendAsync` can either return a promise or
* throw an error, and if it returns a promise, that promise can either be
* resolved with a response object or reject with an error.
*
* @property request - Looks for a request matching these specifications.
* @property request.method - The RPC method to which this stub will be matched.
* @property request.params - The params to which this stub will be matched.
* @property response - Instructs `sendAsync` to return a promise that resolves
* with a response object.
* @property response.result - Specifies a successful response, with this as the
* `result`.
* @property response.error - Specifies an error response, with this as the
* `error`.
* @property error - Instructs `sendAsync` to return a promise that rejects with
* this error.
* @property implementation - Allows overriding `sendAsync` entirely. Useful if
* you want it to throw an error.
* @property delay - The amount of time that will pass after the callback is
* called with the response.
* @property discardAfterMatching - Usually after the stub matches a request, it
* is discarded, but setting this to true prevents that from happening. True by
* default.
* @property beforeCompleting - Sometimes it is useful to do something after the
* request is kicked off but before it ends (or, in terms of a `fetch` promise,
* when the promise is initiated but before it is resolved). You can pass an
* (async) function for this option to do this.
*/
export type FakeProviderStub = {
request: {
method: string;
params?: any[];
};
delay?: number;
discardAfterMatching?: boolean;
beforeCompleting?: () => void | Promise<void>;
} & (
| {
response: { result: any } | { error: string };
}
| {
error: unknown;
}
| {
implementation: () => void;
}
);
/**
* The set of options that the FakeProviderEngine constructor takes.
*
* @property stubs - A set of objects that allow specifying the behavior
* of specific invocations of `sendAsync` matching a `method`.
*/
interface FakeProviderEngineOptions {
stubs?: FakeProviderStub[];
}
/**
* FakeProviderEngine is an implementation of the provider that
* NetworkController exposes, which is actually an instance of
* Web3ProviderEngine (from the `web3-provider-engine` package). Hence it
* supports the same interface as Web3ProviderEngine, except that fake responses
* for any RPC methods that are accessed can be supplied via an API that is more
* succinct than using Jest's mocking API.
*/
// NOTE: We shouldn't need to extend from the "real" provider here, but
// we'd need a `SafeEventEmitterProvider` _interface_ and that doesn't exist (at
// least not yet).
export class FakeProvider extends SafeEventEmitterProvider {
calledStubs: FakeProviderStub[];
#originalStubs: FakeProviderStub[];
#stubs: FakeProviderStub[];
/**
* Makes a new instance of the fake provider.
*
* @param options - The options.
* @param options.stubs - A set of objects that allow specifying the behavior
* of specific invocations of `sendAsync` matching a `method`.
*/
constructor({ stubs = [] }: FakeProviderEngineOptions) {
super({ engine: new JsonRpcEngine() });
this.#originalStubs = stubs;
this.#stubs = this.#originalStubs.slice();
this.calledStubs = [];
}
send = (
payload: JsonRpcRequest<any>,
callback: (error: unknown, response?: JsonRpcResponse<any>) => void,
) => {
return this.#handleSend(payload, callback);
};
sendAsync = (
payload: JsonRpcRequest<any>,
callback: (error: unknown, response?: JsonRpcResponse<any>) => void,
) => {
return this.#handleSend(payload, callback);
};
#handleSend(
payload: JsonRpcRequest<any>,
callback: (error: unknown, response?: JsonRpcResponse<any>) => void,
) {
if (Array.isArray(payload)) {
throw new Error("Arrays aren't supported");
}
const index = this.#stubs.findIndex((stub) => {
return (
stub.request.method === payload.method &&
(!('params' in stub.request) ||
isDeepStrictEqual(stub.request.params, payload.params))
);
});
if (index === -1) {
const matchingCalledStubs = this.calledStubs.filter((stub) => {
return (
stub.request.method === payload.method &&
(!('params' in stub.request) ||
isDeepStrictEqual(stub.request.params, payload.params))
);
});
let message = `Could not find any stubs matching: ${inspect(payload, {
depth: null,
})}`;
if (matchingCalledStubs.length > 0) {
message += `\n\nIt appears the following stubs were defined, but have been called already:\n\n${inspect(
matchingCalledStubs,
{ depth: null },
)}`;
}
throw new Error(message);
} else {
const stub = this.#stubs[index];
if (stub.discardAfterMatching !== false) {
this.#stubs.splice(index, 1);
}
if (stub.delay) {
originalSetTimeout(() => {
this.#handleRequest(stub, callback);
}, stub.delay);
} else {
this.#handleRequest(stub, callback);
}
this.calledStubs.push({ ...stub });
}
}
async #handleRequest(
stub: FakeProviderStub,
callback: (error: unknown, response?: JsonRpcResponse<any>) => void,
) {
if (stub.beforeCompleting) {
await stub.beforeCompleting();
}
if ('implementation' in stub) {
stub.implementation();
return;
}
if ('response' in stub) {
if ('result' in stub.response) {
callback(null, {
jsonrpc: '2.0',
id: 1,
result: stub.response.result,
});
} else if ('error' in stub.response) {
callback(null, {
jsonrpc: '2.0',
id: 1,
error: {
code: -999,
message: stub.response.error,
},
});
}
} else if ('error' in stub) {
callback(stub.error);
}
}
}

View File

@ -1,8 +1,8 @@
import { endowmentPermissionBuilders } from '@metamask/snaps-controllers';
import {
restrictedMethodPermissionBuilders,
selectHooks,
} from '@metamask/rpc-methods';
import { endowmentPermissionBuilders } from '@metamask/snaps-controllers';
import {
ExcludedSnapEndowments,
ExcludedSnapPermissions,

View File

@ -8,14 +8,6 @@ import {
buildSnapRestrictedMethodSpecifications,
} from './snap-permissions';
// Temporarily replace the snaps packages with the Flask versions.
jest.mock('@metamask/snaps-controllers', () =>
jest.requireActual('@metamask/snaps-controllers-flask'),
);
jest.mock('@metamask/rpc-methods', () =>
jest.requireActual('@metamask/rpc-methods-flask'),
);
describe('buildSnapRestrictedMethodSpecifications', () => {
it('creates valid permission specification objects', () => {
const hooks = {

View File

@ -1,12 +1,12 @@
import { ObservableStore } from '@metamask/obs-store';
import { normalize as normalizeAddress } from 'eth-sig-util';
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
import { setDashboardCookie } from '@metamask-institutional/portfolio-dashboard';
///: END:ONLY_INCLUDE_IN
import { IPFS_DEFAULT_GATEWAY_URL } from '../../../shared/constants/network';
import { LedgerTransportTypes } from '../../../shared/constants/hardware-wallets';
import { ThemeType } from '../../../shared/constants/preferences';
import { shouldShowLineaMainnet } from '../../../shared/modules/network.utils';
///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
import { KEYRING_SNAPS_REGISTRY_URL } from '../../../shared/constants/app';
///: END:ONLY_INCLUDE_IN
export default class PreferencesController {
/**
@ -68,8 +68,12 @@ export default class PreferencesController {
ledgerTransportType: window.navigator.hid
? LedgerTransportTypes.webhid
: LedgerTransportTypes.u2f,
snapRegistryList: {},
transactionSecurityCheckEnabled: false,
theme: ThemeType.os,
///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
snapsAddSnapAccountModalDismissed: false,
///: END:ONLY_INCLUDE_IN
isLineaMainnetReleased: false,
...opts.initState,
};
@ -81,14 +85,6 @@ export default class PreferencesController {
this.store.setMaxListeners(13);
this.tokenListController = opts.tokenListController;
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
this.handleMmiDashboardData = opts.handleMmiDashboardData;
if (!process.env.IN_TEST) {
this.mmiConfigurationStore = opts.mmiConfigurationStore.getState();
}
///: END:ONLY_INCLUDE_IN
this._subscribeToInfuraAvailability();
global.setPreference = (key, value) => {
@ -261,10 +257,6 @@ export default class PreferencesController {
return ids;
}, {});
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
this.prepareMmiPortfolio();
///: END:ONLY_INCLUDE_IN
this.store.updateState({ identities });
}
@ -290,10 +282,6 @@ export default class PreferencesController {
this.setSelectedAddress(selected);
}
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
this.prepareMmiPortfolio();
///: END:ONLY_INCLUDE_IN
return address;
}
@ -350,10 +338,6 @@ export default class PreferencesController {
this.store.updateState({ identities, lostIdentities });
this.addAddresses(addresses);
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
this.prepareMmiPortfolio();
///: END:ONLY_INCLUDE_IN
// If the selected account is no longer valid,
// select an arbitrary other account:
let selected = this.getSelectedAddress();
@ -534,18 +518,21 @@ export default class PreferencesController {
return this.store.getState().disabledRpcMethodPreferences;
}
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
async prepareMmiPortfolio() {
if (!process.env.IN_TEST) {
try {
const mmiDashboardData = await this.handleMmiDashboardData();
const cookieSetUrls =
this.mmiConfigurationStore.mmiConfiguration?.portfolio?.cookieSetUrls;
setDashboardCookie(mmiDashboardData, cookieSetUrls);
} catch (error) {
console.error(error);
}
///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps)
setSnapsAddSnapAccountModalDismissed(value) {
this.store.updateState({ snapsAddSnapAccountModalDismissed: value });
}
async updateSnapRegistry() {
let snapRegistry;
try {
const response = await fetch(KEYRING_SNAPS_REGISTRY_URL);
snapRegistry = await response.json();
} catch (error) {
console.error(`Failed to fetch registry: `, error);
snapRegistry = {};
}
this.store.updateState({ snapRegistryList: snapRegistry });
}
///: END:ONLY_INCLUDE_IN

View File

@ -147,9 +147,9 @@ export default class SwapsController {
this.indexOfNewestCallInFlight = 0;
this.ethersProvider = new Web3Provider(provider);
this._currentNetworkId = networkController.store.getState().networkId;
this._currentNetworkId = networkController.state.networkId;
onNetworkStateChange(() => {
const { networkId, networkStatus } = networkController.store.getState();
const { networkId, networkStatus } = networkController.state;
if (
networkStatus === NetworkStatus.Available &&
networkId !== this._currentNetworkId

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