mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-21 17:37:01 +01:00
Merge branch 'develop' of github.com:MetaMask/metamask-extension into minimal
This commit is contained in:
commit
5fbd7afb23
@ -101,6 +101,9 @@ workflows:
|
||||
build-type: [main, beta, flask, mmi, desktop]
|
||||
requires:
|
||||
- prep-deps
|
||||
- prep-build-mmi:
|
||||
requires:
|
||||
- prep-deps
|
||||
- prep-build:
|
||||
requires:
|
||||
- prep-deps
|
||||
@ -186,6 +189,9 @@ workflows:
|
||||
ignore: master
|
||||
requires:
|
||||
- prep-build-desktop
|
||||
- validate-source-maps-mmi:
|
||||
requires:
|
||||
- prep-build-mmi
|
||||
- validate-source-maps-flask:
|
||||
requires:
|
||||
- prep-build-flask
|
||||
@ -222,6 +228,7 @@ workflows:
|
||||
- validate-source-maps-beta
|
||||
- validate-source-maps-desktop
|
||||
- validate-source-maps-flask
|
||||
- validate-source-maps-mmi
|
||||
- test-mozilla-lint
|
||||
- test-mozilla-lint-desktop
|
||||
- test-mozilla-lint-flask
|
||||
@ -245,6 +252,7 @@ workflows:
|
||||
- prep-build
|
||||
- trigger-beta-build
|
||||
- prep-build-desktop
|
||||
- prep-build-mmi
|
||||
- prep-build-flask
|
||||
- prep-build-storybook
|
||||
- prep-build-ts-migration-dashboard
|
||||
@ -261,6 +269,7 @@ workflows:
|
||||
- prep-deps
|
||||
- prep-build
|
||||
- prep-build-desktop
|
||||
- prep-build-mmi
|
||||
- prep-build-flask
|
||||
- all-tests-pass
|
||||
- job-publish-storybook:
|
||||
@ -353,18 +362,15 @@ jobs:
|
||||
echo "Not a PR; skipping"
|
||||
fi
|
||||
- run:
|
||||
name: Setup registry config for using package previews on draft PRs
|
||||
name: Install dependencies
|
||||
command: |
|
||||
if [[ $IS_DRAFT == 'true' ]]
|
||||
then
|
||||
printf '%s\n\n%s' '@metamask:registry=https://npm.pkg.github.com' "//npm.pkg.github.com/:_authToken=${GITHUB_PACKAGE_READ_TOKEN}" > .npmrc
|
||||
# Use GitHub registry on draft PRs, allowing the use of preview builds
|
||||
METAMASK_NPM_REGISTRY=https://npm.pkg.github.com yarn --immutable
|
||||
else
|
||||
echo "Not draft; skipping GitHub registry setup"
|
||||
yarn --immutable
|
||||
fi
|
||||
- run:
|
||||
name: Install deps
|
||||
command: |
|
||||
.circleci/scripts/deps-install.sh
|
||||
- save_cache:
|
||||
key: dependency-cache-v1-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
@ -475,6 +481,49 @@ jobs:
|
||||
- dist-desktop
|
||||
- builds-desktop
|
||||
|
||||
prep-build-mmi:
|
||||
executor: node-browsers-medium-plus
|
||||
steps:
|
||||
- run: *shallow-git-clone
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- when:
|
||||
condition:
|
||||
not:
|
||||
matches:
|
||||
pattern: /^master$/
|
||||
value: << pipeline.git.branch >>
|
||||
steps:
|
||||
- run:
|
||||
name: build:dist
|
||||
command: yarn build --build-type mmi dist
|
||||
- when:
|
||||
condition:
|
||||
matches:
|
||||
pattern: /^master$/
|
||||
value: << pipeline.git.branch >>
|
||||
steps:
|
||||
- run:
|
||||
name: build:prod
|
||||
command: yarn build --build-type mmi prod
|
||||
- run:
|
||||
name: build:debug
|
||||
command: find dist/ -type f -exec md5sum {} \; | sort -k 2
|
||||
- run:
|
||||
name: Move mmi build to 'dist-mmi' to avoid conflict with production build
|
||||
command: mv ./dist ./dist-mmi
|
||||
- run:
|
||||
name: Move mmi zips to 'builds-mmi' to avoid conflict with production build
|
||||
command: mv ./builds ./builds-mmi
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- dist-mmi
|
||||
- builds-mmi
|
||||
- store_artifacts:
|
||||
path: builds-mmi
|
||||
destination: builds-mmi
|
||||
|
||||
prep-build-flask:
|
||||
executor: node-browsers-medium-plus
|
||||
steps:
|
||||
@ -1191,6 +1240,22 @@ jobs:
|
||||
name: Validate source maps
|
||||
command: yarn validate-source-maps
|
||||
|
||||
validate-source-maps-mmi:
|
||||
executor: node-browsers
|
||||
steps:
|
||||
- run: *shallow-git-clone
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Move mmi build to dist
|
||||
command: mv ./dist-mmi ./dist
|
||||
- run:
|
||||
name: Move mmi zips to builds
|
||||
command: mv ./builds-mmi ./builds
|
||||
- run:
|
||||
name: Validate source maps
|
||||
command: yarn validate-source-maps
|
||||
|
||||
validate-source-maps-flask:
|
||||
executor: node-browsers
|
||||
steps:
|
||||
|
@ -1,9 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Print commands and their arguments as they are executed.
|
||||
set -x
|
||||
# Exit immediately if a command exits with a non-zero status.
|
||||
set -e
|
||||
|
||||
yarn install --frozen-lockfile
|
||||
|
@ -72,5 +72,8 @@ module.exports = {
|
||||
// upgrading eslint and dependencies. This rule should be evaluated and
|
||||
// if agreeable turned on upstream in @metamask/eslint-config
|
||||
'import/no-named-as-default-member': 'off',
|
||||
|
||||
// This is necessary to run eslint on Windows and not get a thousand CRLF errors
|
||||
'prettier/prettier': ['error', { endOfLine: 'auto' }],
|
||||
},
|
||||
};
|
||||
|
21
.github/workflows/stale-issues-pr.yml
vendored
21
.github/workflows/stale-issues-pr.yml
vendored
@ -1,7 +1,9 @@
|
||||
name: 'Close stale issues and PRs'
|
||||
|
||||
# run every 2 hours
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 12 * * *'
|
||||
- cron: '0 */2 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
@ -12,17 +14,16 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/stale@72afbce2b0dbd1d903bb142cebe2d15dc307ae57
|
||||
with:
|
||||
stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity in the last 90 days. It will be closed in 45 days. Thank you for your contributions.'
|
||||
stale-issue-label: 'stale'
|
||||
only-issue-labels: 'type-bug'
|
||||
exempt-issue-labels: 'type-security, type-pinned, feature-request, awaiting-metamask'
|
||||
stale-pr-message: 'This PR has been automatically marked as stale because it has not had recent activity in the last 60 days. It will be closed in 14 days. Thank you for your contributions.'
|
||||
stale-pr-label: 'stale'
|
||||
exempt-pr-labels: 'work-in-progress'
|
||||
close-issue-message: 'This issue was closed because there has been no follow up activity in the last 7 days. If you feel this was closed in error, please reopen and provide evidence on the latest release of the extension. Thank you for your contributions.'
|
||||
close-pr-message: 'This PR was closed because there has been no follow up activity in the last 7 days. Thank you for your contributions.'
|
||||
exempt-issue-labels: 'type-security'
|
||||
days-before-issue-stale: 90
|
||||
days-before-pr-stale: 60
|
||||
stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity in the last 90 days. It will be closed in 45 days if there is no further activity. The MetaMask team intends on reviewing this issue before close, and removing the stale label if it is still a bug. We welcome new comments on this issue. We do not intend on closing issues if they report bugs that are still reproducible. Thank you for your contributions.'
|
||||
days-before-issue-close: 45
|
||||
close-issue-message: 'This issue was closed because there has been no follow up activity in the last 45 days. If you feel this was closed in error, please reopen and provide evidence on the latest release of the extension. Thank you for your contributions.'
|
||||
stale-pr-label: 'stale'
|
||||
days-before-pr-stale: 60
|
||||
stale-pr-message: 'This PR has been automatically marked as stale because it has not had recent activity in the last 60 days. It will be closed in 14 days. Thank you for your contributions.'
|
||||
days-before-pr-close: 14
|
||||
operations-per-run: 1
|
||||
close-pr-message: 'This PR was closed because there has been no follow up activity in the last 14 days. Thank you for your contributions.'
|
||||
operations-per-run: 600
|
||||
|
4
.iyarc
4
.iyarc
@ -1,10 +1,6 @@
|
||||
# improved-yarn-audit advisory exclusions
|
||||
GHSA-257v-vj4p-3w2h
|
||||
|
||||
# request library is subject to SSRF.
|
||||
# addressed by temporary patch in .yarn/patches/request-npm-2.88.2-f4a57c72c4.patch
|
||||
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
|
||||
|
@ -12,3 +12,4 @@ INFURA_PROJECT_ID=00000000000
|
||||
|
||||
; Set this to test changes to the phishing warning page.
|
||||
;PHISHING_WARNING_PAGE_URL=
|
||||
BLOCKAID_FILE_CDN=
|
||||
|
@ -1,2 +1,5 @@
|
||||
# All of these are defaults except singleQuote and endOfLine, but we specify them
|
||||
# for explicitness
|
||||
endOfLine: auto
|
||||
singleQuote: true
|
||||
trailingComma: all
|
||||
|
@ -0,0 +1,46 @@
|
||||
diff --git a/dist/KeyringController.d.ts b/dist/KeyringController.d.ts
|
||||
index 82de83a7bb1ad14bb23f3b6274e0c4d5bb773382..86a09b3f604f6feb26e2c7edbdcb0abebd4bae20 100644
|
||||
--- a/dist/KeyringController.d.ts
|
||||
+++ b/dist/KeyringController.d.ts
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { TxData, TypedTransaction } from '@ethereumjs/tx';
|
||||
-import { type MetaMaskKeyring as QRKeyring, type IKeyringState as IQRKeyringState } from '@keystonehq/metamask-airgapped-keyring';
|
||||
+import type { MetaMaskKeyring as QRKeyring, IKeyringState as IQRKeyringState } from '@keystonehq/metamask-airgapped-keyring';
|
||||
import type { RestrictedControllerMessenger } from '@metamask/base-controller';
|
||||
import { BaseControllerV2 } from '@metamask/base-controller';
|
||||
import type { PersonalMessageParams, TypedMessageParams } from '@metamask/message-manager';
|
||||
import type { PreferencesController } from '@metamask/preferences-controller';
|
||||
-import { type Hex, type Keyring, type Json } from '@metamask/utils';
|
||||
+import type { Hex, Keyring, Json } from '@metamask/utils';
|
||||
+import type { KeyringController as EthKeyringController } from '@metamask/eth-keyring-controller';
|
||||
import type { Patch } from 'immer';
|
||||
declare const name = "KeyringController";
|
||||
/**
|
||||
@@ -135,6 +136,10 @@ export declare class KeyringController extends BaseControllerV2<typeof name, Key
|
||||
* @param opts.state - Initial state to set on this controller.
|
||||
*/
|
||||
constructor({ removeIdentity, syncIdentities, updateIdentities, setSelectedAddress, setAccountLabel, encryptor, keyringBuilders, cacheEncryptionKey, messenger, state, }: KeyringControllerOptions);
|
||||
+ /**
|
||||
+ * Gets the internal keyring controller.
|
||||
+ */
|
||||
+ getEthKeyringController(): EthKeyringController;
|
||||
/**
|
||||
* Adds a new account to the default (first) HD seed phrase keyring.
|
||||
*
|
||||
diff --git a/dist/KeyringController.js b/dist/KeyringController.js
|
||||
index 54d39d266425b45ed1008cecb16e78cf831c75d7..0ddded415bf71716c27ed3bf7bd1c5a79b11be13 100644
|
||||
--- a/dist/KeyringController.js
|
||||
+++ b/dist/KeyringController.js
|
||||
@@ -153,6 +153,12 @@ class KeyringController extends base_controller_1.BaseControllerV2 {
|
||||
this.setSelectedAddress = setSelectedAddress;
|
||||
this.setAccountLabel = setAccountLabel;
|
||||
}
|
||||
+ /**
|
||||
+ * Gets the internal keyring controller.
|
||||
+ */
|
||||
+ getEthKeyringController() {
|
||||
+ return __classPrivateFieldGet(this, _KeyringController_keyring, "f");
|
||||
+ }
|
||||
/**
|
||||
* Adds a new account to the default (first) HD seed phrase keyring.
|
||||
*
|
@ -1,17 +0,0 @@
|
||||
diff --git a/dist/SignatureController.js b/dist/SignatureController.js
|
||||
index b58b27e84aa84393afb366d4585c084d0380d21d..0629bcf517db744ccfa40e4d7d8f2829fa95559e 100644
|
||||
--- a/dist/SignatureController.js
|
||||
+++ b/dist/SignatureController.js
|
||||
@@ -237,8 +237,11 @@ _SignatureController_keyringController = new WeakMap(), _SignatureController_isE
|
||||
yield __classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_requestApproval).call(this, messageParamsWithId, approvalType);
|
||||
}
|
||||
catch (error) {
|
||||
+ signaturePromise.catch(() => {
|
||||
+ // Expecting reject error but throwing manually rather than waiting
|
||||
+ });
|
||||
__classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_cancelAbstractMessage).call(this, messageManager, messageId);
|
||||
- throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest('User rejected the request.');
|
||||
+ throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest(`MetaMask ${messageName} Signature: User denied message signature.`);
|
||||
}
|
||||
yield signMessage(messageParamsWithId, version, signingOpts);
|
||||
return signaturePromise;
|
@ -0,0 +1,26 @@
|
||||
diff --git a/dist/SignatureController.js b/dist/SignatureController.js
|
||||
index a2f064efa2a2700db00767daa4ce6bd22b1932c4..17edb51b6c526f27fb4c19f2d2fda3d7140c66b4 100644
|
||||
--- a/dist/SignatureController.js
|
||||
+++ b/dist/SignatureController.js
|
||||
@@ -283,8 +283,11 @@ _SignatureController_keyringController = new WeakMap(), _SignatureController_isE
|
||||
resultCallbacks = acceptResult.resultCallbacks;
|
||||
}
|
||||
catch (_a) {
|
||||
+ signaturePromise.catch(() => {
|
||||
+ // Expecting reject error but throwing manually rather than waiting
|
||||
+ });
|
||||
__classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_cancelAbstractMessage).call(this, messageManager, messageId);
|
||||
- throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest('User rejected the request.');
|
||||
+ throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest(`MetaMask ${messageName} Signature: User denied message signature.`);
|
||||
}
|
||||
yield signMessage(messageParamsWithId, signingOpts);
|
||||
const signatureResult = yield signaturePromise;
|
||||
@@ -305,7 +308,7 @@ _SignatureController_keyringController = new WeakMap(), _SignatureController_isE
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
return yield __classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_signAbstractMessage).call(this, __classPrivateFieldGet(this, _SignatureController_personalMessageManager, "f"), controller_utils_1.ApprovalType.PersonalSign, msgParams, (cleanMsgParams) => __awaiter(this, void 0, void 0, function* () { return yield __classPrivateFieldGet(this, _SignatureController_keyringController, "f").signPersonalMessage(cleanMsgParams); }));
|
||||
});
|
||||
-}, _SignatureController_signTypedMessage = function _SignatureController_signTypedMessage(msgParams,
|
||||
+}, _SignatureController_signTypedMessage = function _SignatureController_signTypedMessage(msgParams,
|
||||
/* istanbul ignore next */
|
||||
opts = { parseJsonData: true }) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
80
.yarn/patches/jsdom-npm-16.7.0-216c5c4bf9.patch
Normal file
80
.yarn/patches/jsdom-npm-16.7.0-216c5c4bf9.patch
Normal file
@ -0,0 +1,80 @@
|
||||
diff --git a/lib/jsdom/browser/Window.js b/lib/jsdom/browser/Window.js
|
||||
index 9b2d75f55050f865382e2f0e8a88f066e0bff2da..d4a635da8eae02eaf0543693356f1252f8b6bac0 100644
|
||||
--- a/lib/jsdom/browser/Window.js
|
||||
+++ b/lib/jsdom/browser/Window.js
|
||||
@@ -24,7 +24,6 @@ const External = require("../living/generated/External");
|
||||
const Navigator = require("../living/generated/Navigator");
|
||||
const Performance = require("../living/generated/Performance");
|
||||
const Screen = require("../living/generated/Screen");
|
||||
-const Storage = require("../living/generated/Storage");
|
||||
const Selection = require("../living/generated/Selection");
|
||||
const reportException = require("../living/helpers/runtime-script-errors");
|
||||
const { getCurrentEventHandlerValue } = require("../living/helpers/create-event-accessor.js");
|
||||
@@ -285,40 +284,6 @@ function Window(options) {
|
||||
this._pretendToBeVisual = options.pretendToBeVisual;
|
||||
this._storageQuota = options.storageQuota;
|
||||
|
||||
- // Some properties (such as localStorage and sessionStorage) share data
|
||||
- // between windows in the same origin. This object is intended
|
||||
- // to contain such data.
|
||||
- if (options.commonForOrigin && options.commonForOrigin[documentOrigin]) {
|
||||
- this._commonForOrigin = options.commonForOrigin;
|
||||
- } else {
|
||||
- this._commonForOrigin = {
|
||||
- [documentOrigin]: {
|
||||
- localStorageArea: new Map(),
|
||||
- sessionStorageArea: new Map(),
|
||||
- windowsInSameOrigin: [this]
|
||||
- }
|
||||
- };
|
||||
- }
|
||||
-
|
||||
- this._currentOriginData = this._commonForOrigin[documentOrigin];
|
||||
-
|
||||
- // ### WEB STORAGE
|
||||
-
|
||||
- this._localStorage = Storage.create(window, [], {
|
||||
- associatedWindow: this,
|
||||
- storageArea: this._currentOriginData.localStorageArea,
|
||||
- type: "localStorage",
|
||||
- url: this._document.documentURI,
|
||||
- storageQuota: this._storageQuota
|
||||
- });
|
||||
- this._sessionStorage = Storage.create(window, [], {
|
||||
- associatedWindow: this,
|
||||
- storageArea: this._currentOriginData.sessionStorageArea,
|
||||
- type: "sessionStorage",
|
||||
- url: this._document.documentURI,
|
||||
- storageQuota: this._storageQuota
|
||||
- });
|
||||
-
|
||||
// ### SELECTION
|
||||
|
||||
// https://w3c.github.io/selection-api/#dfn-selection
|
||||
@@ -416,26 +381,6 @@ function Window(options) {
|
||||
configurable: true
|
||||
});
|
||||
},
|
||||
- get localStorage() {
|
||||
- if (idlUtils.implForWrapper(this._document)._origin === "null") {
|
||||
- throw DOMException.create(window, [
|
||||
- "localStorage is not available for opaque origins",
|
||||
- "SecurityError"
|
||||
- ]);
|
||||
- }
|
||||
-
|
||||
- return this._localStorage;
|
||||
- },
|
||||
- get sessionStorage() {
|
||||
- if (idlUtils.implForWrapper(this._document)._origin === "null") {
|
||||
- throw DOMException.create(window, [
|
||||
- "sessionStorage is not available for opaque origins",
|
||||
- "SecurityError"
|
||||
- ]);
|
||||
- }
|
||||
-
|
||||
- return this._sessionStorage;
|
||||
- },
|
||||
get customElements() {
|
||||
return customElementRegistry;
|
||||
},
|
18
.yarn/patches/lavamoat-core-npm-14.2.0-c453f4f755.patch
Normal file
18
.yarn/patches/lavamoat-core-npm-14.2.0-c453f4f755.patch
Normal file
@ -0,0 +1,18 @@
|
||||
diff --git a/src/loadPolicy.js b/src/loadPolicy.js
|
||||
index ef71923f9282d6a5e9f74e6ec6fa0516f28f508b..0118fda7e1b0fa461ec01ceff8d7112d072f3dfb 100644
|
||||
--- a/src/loadPolicy.js
|
||||
+++ b/src/loadPolicy.js
|
||||
@@ -33,10 +33,9 @@ async function loadPolicyAndApplyOverrides({ debugMode, policyPath, policyOverri
|
||||
}
|
||||
const policyOverride = await readPolicyFile({ debugMode, policyPath: policyOverridePath })
|
||||
lavamoatPolicy = mergePolicy(policy, policyOverride)
|
||||
- // TODO: Only write if merge results in changes.
|
||||
- // Would have to make a deep equal check on whole policy, which is a waste of time.
|
||||
- // mergePolicy() should be able to do it in one pass.
|
||||
- fs.writeFileSync(policyPath, jsonStringify(lavamoatPolicy, { space: 2 }))
|
||||
+ // Skip policy write step to prevent intermittent build failures
|
||||
+ // The extension validates the policy in a separate step, we don't need it
|
||||
+ // to be written to disk here.
|
||||
}
|
||||
return lavamoatPolicy
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
diff --git a/dist/index.d.ts b/dist/index.d.ts
|
||||
index 81253d38280bb25de1e36443d919f0e95b3e023c..d2333bf6796ff3ec94f5857d23ef34cc39c9729a 100644
|
||||
index 66eb3236059f88f73355d4fddef9e06a0169b407..04f067d2bda8af760c0a95ca6b5d85bcdfb2421a 100644
|
||||
--- a/dist/index.d.ts
|
||||
+++ b/dist/index.d.ts
|
||||
@@ -1,10 +1,10 @@
|
||||
@@ -1,11 +1,12 @@
|
||||
-import { type ParserOptions } from './parser/index.js';
|
||||
+import { ParserOptions } from './parser/index.js';
|
||||
import type { DefaultTreeAdapterMap } from './tree-adapters/default.js';
|
||||
@ -12,12 +12,15 @@ index 81253d38280bb25de1e36443d919f0e95b3e023c..d2333bf6796ff3ec94f5857d23ef34cc
|
||||
export type { TreeAdapter, TreeAdapterTypeMap } from './tree-adapters/interface.js';
|
||||
-export { type ParserOptions, /** @internal */ Parser } from './parser/index.js';
|
||||
-export { serialize, serializeOuter, type SerializerOptions } from './serializer/index.js';
|
||||
-export { ERR as ErrorCodes, type ParserError } from './common/error-codes.js';
|
||||
+export { ParserOptions, /** @internal */ Parser } from './parser/index.js';
|
||||
+export { serialize, serializeOuter, SerializerOptions } from './serializer/index.js';
|
||||
export type { ParserError } from './common/error-codes.js';
|
||||
+export type { ParserError } from './common/error-codes.js';
|
||||
+export { ERR as ErrorCodes } from './common/error-codes.js';
|
||||
/** @internal */
|
||||
export * as foreignContent from './common/foreign-content.js';
|
||||
@@ -13,7 +13,7 @@ export * as html from './common/html.js';
|
||||
/** @internal */
|
||||
@@ -13,7 +14,7 @@ export * as html from './common/html.js';
|
||||
/** @internal */
|
||||
export * as Token from './common/token.js';
|
||||
/** @internal */
|
||||
@ -27,7 +30,7 @@ index 81253d38280bb25de1e36443d919f0e95b3e023c..d2333bf6796ff3ec94f5857d23ef34cc
|
||||
* Parses an HTML string.
|
||||
*
|
||||
diff --git a/dist/parser/index.d.ts b/dist/parser/index.d.ts
|
||||
index 50a9bd0c73649e4a78edd0d18b4ee44ae9cdf3b7..df1863e335e64269298dea42a7481b26b9e77581 100644
|
||||
index 50a9bd0c73649e4a78edd0d18b4ee44ae9cdf3b7..85cc630db81d7a4ebd01691223d81187cdd8adcb 100644
|
||||
--- a/dist/parser/index.d.ts
|
||||
+++ b/dist/parser/index.d.ts
|
||||
@@ -1,10 +1,10 @@
|
||||
@ -46,18 +49,18 @@ index 50a9bd0c73649e4a78edd0d18b4ee44ae9cdf3b7..df1863e335e64269298dea42a7481b26
|
||||
INITIAL = 0,
|
||||
BEFORE_HTML = 1,
|
||||
diff --git a/dist/serializer/index.d.ts b/dist/serializer/index.d.ts
|
||||
index d944fae103a245cb84623fd733c91cc7e79f72f1..432464c9e05ecfd93c66526bcf4f0c81f09bf00d 100644
|
||||
index bf759e63ba1be31a2ab14884fcfd6bd3e8ecd2d7..839e21e45dc13e678c9874c51524a8ed1a463591 100644
|
||||
--- a/dist/serializer/index.d.ts
|
||||
+++ b/dist/serializer/index.d.ts
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TreeAdapter, TreeAdapterTypeMap } from '../tree-adapters/interface';
|
||||
import type { TreeAdapter, TreeAdapterTypeMap } from '../tree-adapters/interface.js';
|
||||
-import { type DefaultTreeAdapterMap } from '../tree-adapters/default.js';
|
||||
+import { DefaultTreeAdapterMap } from '../tree-adapters/default.js';
|
||||
export interface SerializerOptions<T extends TreeAdapterTypeMap> {
|
||||
/**
|
||||
* Specifies input tree format.
|
||||
diff --git a/dist/tokenizer/index.d.ts b/dist/tokenizer/index.d.ts
|
||||
index de6e234cfb36bb3a4b928c47ab0d0fdf0b4311e1..89e2484b43f3487f3f157435a283ba932a879210 100644
|
||||
index 5afab96d6499bb0bba706aee7d2f153647db8713..3680d732d8a3570b6a1d9336c0ebdf8fe4f392db 100644
|
||||
--- a/dist/tokenizer/index.d.ts
|
||||
+++ b/dist/tokenizer/index.d.ts
|
||||
@@ -1,6 +1,6 @@
|
||||
@ -69,8 +72,18 @@ index de6e234cfb36bb3a4b928c47ab0d0fdf0b4311e1..89e2484b43f3487f3f157435a283ba93
|
||||
declare const enum State {
|
||||
DATA = 0,
|
||||
RCDATA = 1,
|
||||
diff --git a/dist/tree-adapters/default.d.ts b/dist/tree-adapters/default.d.ts
|
||||
index 547d714bdc5a664ba1414c16bdfc9247c71ab4de..d96a23d6ce3e80d78da21d958c059de194bb5146 100644
|
||||
--- a/dist/tree-adapters/default.d.ts
|
||||
+++ b/dist/tree-adapters/default.d.ts
|
||||
@@ -1,4 +1,4 @@
|
||||
-import { DOCUMENT_MODE, type NS } from '../common/html.js';
|
||||
+import { DOCUMENT_MODE, NS } from '../common/html.js';
|
||||
import type { Attribute, Location, ElementLocation } from '../common/token.js';
|
||||
import type { TreeAdapter, TreeAdapterTypeMap } from './interface.js';
|
||||
export interface Document {
|
||||
diff --git a/dist/tokenizer/preprocessor.d.ts b/dist/tokenizer/preprocessor.d.ts
|
||||
index e74a590783b4688fb6498b019c1a75cfd9ac23e7..d145dcce97b104830e5b3d7f57f3a63377bbf89c 100644
|
||||
index e74a590783b4688fb6498b019c1a75cfd9ac23e7..7350e44b4ed952bcb8f167e30a94958e9fcf743a 100644
|
||||
--- a/dist/tokenizer/preprocessor.d.ts
|
||||
+++ b/dist/tokenizer/preprocessor.d.ts
|
||||
@@ -1,4 +1,4 @@
|
||||
@ -79,13 +92,3 @@ index e74a590783b4688fb6498b019c1a75cfd9ac23e7..d145dcce97b104830e5b3d7f57f3a633
|
||||
export declare class Preprocessor {
|
||||
private handler;
|
||||
html: string;
|
||||
diff --git a/dist/tree-adapters/default.d.ts b/dist/tree-adapters/default.d.ts
|
||||
index cccdf8f86d2295b3059c42943d896e81691c8419..d70b8fa2562f4dc6415d7ebaaba6cb322f66e9cb 100644
|
||||
--- a/dist/tree-adapters/default.d.ts
|
||||
+++ b/dist/tree-adapters/default.d.ts
|
||||
@@ -1,4 +1,4 @@
|
||||
-import { DOCUMENT_MODE, type NS } from '../common/html.js';
|
||||
+import { DOCUMENT_MODE, NS } from '../common/html.js';
|
||||
import type { Attribute, Location, ElementLocation } from '../common/token.js';
|
||||
import type { TreeAdapter, TreeAdapterTypeMap } from './interface.js';
|
||||
export declare enum NodeType {
|
@ -8,6 +8,15 @@ logFilters:
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
npmRegistries:
|
||||
"https://npm.pkg.github.com":
|
||||
npmAlwaysAuth: true
|
||||
npmAuthToken: "${GITHUB_PACKAGE_READ_TOKEN-}"
|
||||
|
||||
npmScopes:
|
||||
metamask:
|
||||
npmRegistryServer: "${METAMASK_NPM_REGISTRY:-https://registry.yarnpkg.com}"
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-allow-scripts.cjs
|
||||
spec: "https://raw.githubusercontent.com/LavaMoat/LavaMoat/main/packages/yarn-plugin-allow-scripts/bundles/@yarnpkg/plugin-allow-scripts.js"
|
||||
|
33
CHANGELOG.md
33
CHANGELOG.md
@ -6,6 +6,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [10.34.1]
|
||||
### Fixed
|
||||
- Fix bug that could cause a failure in the persistence of network related data ([#20080](https://github.com/MetaMask/metamask-extension/pull/20080))
|
||||
- Fix ([#20080](https://github.com/MetaMask/metamask-extension/pull/20080))
|
||||
|
||||
## [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))
|
||||
@ -3829,7 +3858,9 @@ 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.1...HEAD
|
||||
[10.34.1]: https://github.com/MetaMask/metamask-extension/compare/v10.34.0...v10.34.1
|
||||
[10.34.0]: https://github.com/MetaMask/metamask-extension/compare/v10.33.1...v10.34.0
|
||||
[10.33.1]: https://github.com/MetaMask/metamask-extension/compare/v10.33.0...v10.33.1
|
||||
[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
|
||||
|
3
app/_locales/am/messages.json
generated
3
app/_locales/am/messages.json
generated
@ -545,9 +545,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "የግል አውታረ መረብ"
|
||||
},
|
||||
"queue": {
|
||||
"message": "ወረፋ"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "በመለያ አማራጮችዎ ምናሌ ውስጥ ወደ “ተለዋጭ ስም አክል” በመግባት ለወደፊቱ ይህን ተለዋጭ ስም መልሰው ማከል ይችላሉ።"
|
||||
},
|
||||
|
3
app/_locales/ar/messages.json
generated
3
app/_locales/ar/messages.json
generated
@ -557,9 +557,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "شبكة خاصة"
|
||||
},
|
||||
"queue": {
|
||||
"message": "اللائحة"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "يمكنك إضافة هذه العملة الرمزية مرة أخرى في المستقبل من خلال الانتقال إلى \"إضافة عملة رمزية\" في قائمة خيارات الحسابات الخاصة بك."
|
||||
},
|
||||
|
3
app/_locales/bg/messages.json
generated
3
app/_locales/bg/messages.json
generated
@ -556,9 +556,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Частна мрежа"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Опашка"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Можете да добавите този жетон в бъдеще, като отидете на „Добавяне на жетон“ в менюто с опции на акаунти."
|
||||
},
|
||||
|
3
app/_locales/bn/messages.json
generated
3
app/_locales/bn/messages.json
generated
@ -554,9 +554,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "ব্যক্তিগত নেটওয়ার্ক"
|
||||
},
|
||||
"queue": {
|
||||
"message": "অপেক্ষমাণ"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "আপনি আপনার অ্যাকাউন্টস বিকল্পের মেনুতে \"টোকেনগুলি যোগ করুন\" এ গিয়ে ভবিষ্যতে আবার এই টোকেনটি যোগ করতে পারবেন। "
|
||||
},
|
||||
|
3
app/_locales/ca/messages.json
generated
3
app/_locales/ca/messages.json
generated
@ -541,9 +541,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Xarxa privada"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Cua"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Pots tornar a afegir aquesta fitxa en el futur anant a \"Afegir fitxa\" al menu d'opcions dels teus comptes."
|
||||
},
|
||||
|
3
app/_locales/da/messages.json
generated
3
app/_locales/da/messages.json
generated
@ -541,9 +541,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Privat netværk"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Kø"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Du kan tilføje denne token i fremtiden, ved at gå til \"Tilføj token\" under dine valgmenuen for dine konti."
|
||||
},
|
||||
|
3
app/_locales/de/messages.json
generated
3
app/_locales/de/messages.json
generated
@ -2753,9 +2753,6 @@
|
||||
"publicAddress": {
|
||||
"message": "Öffentliche Adresse"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Warteschlange"
|
||||
},
|
||||
"queued": {
|
||||
"message": "In Warteschlange"
|
||||
},
|
||||
|
3
app/_locales/el/messages.json
generated
3
app/_locales/el/messages.json
generated
@ -2753,9 +2753,6 @@
|
||||
"publicAddress": {
|
||||
"message": "Δημόσια Διεύθυνση"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Ουρά"
|
||||
},
|
||||
"queued": {
|
||||
"message": "Σε Αναμονή"
|
||||
},
|
||||
|
173
app/_locales/en/messages.json
generated
173
app/_locales/en/messages.json
generated
@ -153,9 +153,6 @@
|
||||
"accountSelectionRequired": {
|
||||
"message": "You need to select an account!"
|
||||
},
|
||||
"activated": {
|
||||
"message": "Active"
|
||||
},
|
||||
"active": {
|
||||
"message": "Active"
|
||||
},
|
||||
@ -249,6 +246,9 @@
|
||||
"addFromAListOfPopularNetworks": {
|
||||
"message": "Add from a list of popular networks or add a network manually. Only interact with the entities you trust."
|
||||
},
|
||||
"addHardwareWallet": {
|
||||
"message": "Add hardware wallet"
|
||||
},
|
||||
"addMemo": {
|
||||
"message": "Add memo"
|
||||
},
|
||||
@ -373,9 +373,6 @@
|
||||
"message": "Allow $1 to withdraw and spend up to the following amount:",
|
||||
"description": "The url of the site that requested permission to 'withdraw and spend'"
|
||||
},
|
||||
"amlCompliance": {
|
||||
"message": "AML/CFT Compliance"
|
||||
},
|
||||
"amount": {
|
||||
"message": "Amount"
|
||||
},
|
||||
@ -528,6 +525,9 @@
|
||||
"message": "This is a beta version. Please report bugs $1",
|
||||
"description": "$1 represents the word 'here' in a hyperlink"
|
||||
},
|
||||
"betaMetamaskInstitutionalVersion": {
|
||||
"message": "MetaMask Institutional Beta Version"
|
||||
},
|
||||
"betaMetamaskVersion": {
|
||||
"message": "MetaMask Beta Version"
|
||||
},
|
||||
@ -565,6 +565,30 @@
|
||||
"blockaid": {
|
||||
"message": "Blockaid"
|
||||
},
|
||||
"blockaidDescriptionApproveFarming": {
|
||||
"message": "If you approve this request, a third party known for scams might take all your assets."
|
||||
},
|
||||
"blockaidDescriptionBlurFarming": {
|
||||
"message": "If you approve this request, someone can steal your assets listed on Blur."
|
||||
},
|
||||
"blockaidDescriptionMaliciousDomain": {
|
||||
"message": "You're interacting with a malicious domain. If you approve this request, you might lose your assets."
|
||||
},
|
||||
"blockaidDescriptionMightLoseAssets": {
|
||||
"message": "If you approve this request, you might lose your assets."
|
||||
},
|
||||
"blockaidDescriptionSeaportFarming": {
|
||||
"message": "If you approve this request, someone can steal your assets listed on OpenSea."
|
||||
},
|
||||
"blockaidDescriptionTransferFarming": {
|
||||
"message": "If you approve this request, a third party known for scams will take all your assets."
|
||||
},
|
||||
"blockaidTitleDeceptive": {
|
||||
"message": "This is a deceptive request"
|
||||
},
|
||||
"blockaidTitleSuspicious": {
|
||||
"message": "This is a suspicious request"
|
||||
},
|
||||
"blockies": {
|
||||
"message": "Blockies"
|
||||
},
|
||||
@ -670,45 +694,9 @@
|
||||
"close": {
|
||||
"message": "Close"
|
||||
},
|
||||
"codefiCompliance": {
|
||||
"message": "Codefi Compliance"
|
||||
},
|
||||
"coingecko": {
|
||||
"message": "CoinGecko"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"complianceActivatedTitle": {
|
||||
"message": "Your compliance feature is activated"
|
||||
},
|
||||
"complianceBlurb0": {
|
||||
"message": "DeFi raises AML/CFT risk for institutions, given the decentralised pools and pseudonymous counterparties."
|
||||
},
|
||||
"complianceBlurb1": {
|
||||
"message": "Codefi Compliance is the only product capable of running AML/CFT analysis on DeFi pools. This allows you to identify and avoid pools and counterparties that fail your risk setting."
|
||||
},
|
||||
"complianceBlurbStep1": {
|
||||
"message": "Sign up to Codefi Compliance below"
|
||||
},
|
||||
"complianceBlurbStep2": {
|
||||
"message": "Create an organisation"
|
||||
},
|
||||
"complianceBlurbStep3": {
|
||||
"message": "Create a project"
|
||||
},
|
||||
"complianceBlurbStep4": {
|
||||
"message": "Set your compliance settings"
|
||||
},
|
||||
"complianceBlurbStep5": {
|
||||
"message": "Click the \"Enable Compliance in MMI\" button"
|
||||
},
|
||||
"complianceBlurpStep0": {
|
||||
"message": "Steps to enable AML/CFT Compliance:"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
@ -832,9 +820,6 @@
|
||||
"connectingToSepolia": {
|
||||
"message": "Connecting to Sepolia test network"
|
||||
},
|
||||
"connectionError": {
|
||||
"message": "Connection error"
|
||||
},
|
||||
"connectionFailed": {
|
||||
"message": "Connection failed"
|
||||
},
|
||||
@ -982,6 +967,12 @@
|
||||
"custodianAccount": {
|
||||
"message": "Custodian account"
|
||||
},
|
||||
"custodianAccountAddedDesc": {
|
||||
"message": "You can now use your custodian accounts in MetaMask Institutional."
|
||||
},
|
||||
"custodianAccountAddedTitle": {
|
||||
"message": "Selected custodian accounts have been added."
|
||||
},
|
||||
"custodianReplaceRefreshTokenChangedFailed": {
|
||||
"message": "Please go to $1 and click the 'Connect to MMI' button within their user interface to connect your accounts to MMI again."
|
||||
},
|
||||
@ -1749,9 +1740,6 @@
|
||||
"hardware": {
|
||||
"message": "Hardware"
|
||||
},
|
||||
"hardwareWallet": {
|
||||
"message": "Hardware wallet"
|
||||
},
|
||||
"hardwareWalletConnected": {
|
||||
"message": "Hardware wallet connected"
|
||||
},
|
||||
@ -1765,8 +1753,11 @@
|
||||
"hardwareWallets": {
|
||||
"message": "Connect a hardware wallet"
|
||||
},
|
||||
"hardwareWalletsInfo": {
|
||||
"message": "Hardware wallet integrations use API calls to external servers, which can see your IP address and the smart contract addresses you interact with."
|
||||
},
|
||||
"hardwareWalletsMsg": {
|
||||
"message": "Select a hardware wallet you'd like to use with MetaMask."
|
||||
"message": "Select a hardware wallet you would like to use with MetaMask."
|
||||
},
|
||||
"here": {
|
||||
"message": "here",
|
||||
@ -1965,9 +1956,6 @@
|
||||
"message": "Installed on $1",
|
||||
"description": "$1 is the date when the snap has been installed"
|
||||
},
|
||||
"institutionalFeatures": {
|
||||
"message": "Institutional Features"
|
||||
},
|
||||
"insufficientBalance": {
|
||||
"message": "Insufficient balance."
|
||||
},
|
||||
@ -2262,6 +2250,9 @@
|
||||
"metaMaskConnectStatusParagraphTwo": {
|
||||
"message": "The connection status button shows if the website you’re visiting is connected to your currently selected account."
|
||||
},
|
||||
"metamaskInstitutionalVersion": {
|
||||
"message": "MetaMask Institutional Version"
|
||||
},
|
||||
"metamaskSwapsOfflineDescription": {
|
||||
"message": "MetaMask Swaps is undergoing maintenance. Please check back later."
|
||||
},
|
||||
@ -2303,8 +2294,8 @@
|
||||
"mmiAddToken": {
|
||||
"message": "The page at $1 would like to authorise the following custodian token in MetaMask Institutional"
|
||||
},
|
||||
"mmiAuthenticate": {
|
||||
"message": "The page at $1 would like to authorise the following project’s compliance settings in MetaMask Institutional"
|
||||
"mmiBuiltAroundTheWorld": {
|
||||
"message": "MetaMask Institutional is designed and built around the world."
|
||||
},
|
||||
"more": {
|
||||
"message": "more"
|
||||
@ -2529,9 +2520,6 @@
|
||||
"noNFTs": {
|
||||
"message": "No NFTs yet"
|
||||
},
|
||||
"noReport": {
|
||||
"message": "No Report"
|
||||
},
|
||||
"noSnaps": {
|
||||
"message": "You don't have any snaps installed."
|
||||
},
|
||||
@ -2973,9 +2961,6 @@
|
||||
"onlyConnectTrust": {
|
||||
"message": "Only connect with sites you trust."
|
||||
},
|
||||
"openCodefiCompliance": {
|
||||
"message": "Open Codefi Compliance"
|
||||
},
|
||||
"openFullScreenForLedgerWebHid": {
|
||||
"message": "Go to full screen to connect your Ledger.",
|
||||
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
|
||||
@ -3071,9 +3056,6 @@
|
||||
"message": "You have (1) pending transaction.",
|
||||
"description": "$1 is count of pending transactions"
|
||||
},
|
||||
"percentage": {
|
||||
"message": "$1%"
|
||||
},
|
||||
"permissionRequest": {
|
||||
"message": "Permission request"
|
||||
},
|
||||
@ -3266,9 +3248,6 @@
|
||||
"portfolioDashboard": {
|
||||
"message": "Portfolio Dashboard"
|
||||
},
|
||||
"portfolioView": {
|
||||
"message": "Portfolio view"
|
||||
},
|
||||
"preferredLedgerConnectionType": {
|
||||
"message": "Preferred Ledger connection type",
|
||||
"description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message"
|
||||
@ -3314,12 +3293,6 @@
|
||||
"proceedWithTransaction": {
|
||||
"message": "I want to proceed anyway"
|
||||
},
|
||||
"projectIdInvalid": {
|
||||
"message": "Provided Project ID is invalid"
|
||||
},
|
||||
"projectName": {
|
||||
"message": "Project Name"
|
||||
},
|
||||
"proposedApprovalLimit": {
|
||||
"message": "Proposed approval limit"
|
||||
},
|
||||
@ -3329,9 +3302,6 @@
|
||||
"publicAddress": {
|
||||
"message": "Public address"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Queue"
|
||||
},
|
||||
"queued": {
|
||||
"message": "Queued"
|
||||
},
|
||||
@ -3441,12 +3411,6 @@
|
||||
"replace": {
|
||||
"message": "replace"
|
||||
},
|
||||
"reportLastRun": {
|
||||
"message": "Report last run"
|
||||
},
|
||||
"reportLastRunTooltip": {
|
||||
"message": "The date and time of when the last AML/CFT report was run"
|
||||
},
|
||||
"requestFlaggedAsMaliciousFallbackCopyReason": {
|
||||
"message": "The security provider has not shared additional details"
|
||||
},
|
||||
@ -3504,6 +3468,18 @@
|
||||
"restoreUserDataDescription": {
|
||||
"message": "You can restore user settings containing preferences and account addresses from a previously backed up JSON file."
|
||||
},
|
||||
"resultPageError": {
|
||||
"message": "Error"
|
||||
},
|
||||
"resultPageErrorDefaultMessage": {
|
||||
"message": "The operation failed."
|
||||
},
|
||||
"resultPageSuccess": {
|
||||
"message": "Success"
|
||||
},
|
||||
"resultPageSuccessDefaultMessage": {
|
||||
"message": "The operation completed successfully."
|
||||
},
|
||||
"retryTransaction": {
|
||||
"message": "Retry transaction"
|
||||
},
|
||||
@ -3573,18 +3549,9 @@
|
||||
"revokeSpendingCapTooltipText": {
|
||||
"message": "This third party will be unable to spend any more of your current or future tokens."
|
||||
},
|
||||
"riskRating": {
|
||||
"message": "Risk rating"
|
||||
},
|
||||
"riskRatingTooltip": {
|
||||
"message": "The risk rating of the address you are interacting with based on your risk settings"
|
||||
},
|
||||
"rpcUrl": {
|
||||
"message": "New RPC URL"
|
||||
},
|
||||
"runReport": {
|
||||
"message": "Run report"
|
||||
},
|
||||
"safeTransferFrom": {
|
||||
"message": "Safe transfer from"
|
||||
},
|
||||
@ -3624,9 +3591,25 @@
|
||||
"securityAlert": {
|
||||
"message": "Security alert from $1 and $2"
|
||||
},
|
||||
"securityAlerts": {
|
||||
"message": "Security alerts"
|
||||
},
|
||||
"securityAlertsDescription1": {
|
||||
"message": "Enable this to have your transactions and signature requests reviewed locally (no data shared with third parties) and warnings displayed when malicious activity is detected."
|
||||
},
|
||||
"securityAlertsDescription2": {
|
||||
"message": "Always be sure to do your own due diligence before approving any requests. There's no guarantee all mailcious activity will be detected by this feature."
|
||||
},
|
||||
"securityAndPrivacy": {
|
||||
"message": "Security & privacy"
|
||||
},
|
||||
"securityProviderAdviceBy": {
|
||||
"message": "Security advice by $1",
|
||||
"description": "The security provider that is providing data"
|
||||
},
|
||||
"seeDetails": {
|
||||
"message": "See details"
|
||||
},
|
||||
"seedPhraseConfirm": {
|
||||
"message": "Confirm Secret Recovery Phrase"
|
||||
},
|
||||
@ -3809,9 +3792,6 @@
|
||||
"showPrivateKeys": {
|
||||
"message": "Show Private Keys"
|
||||
},
|
||||
"showReport": {
|
||||
"message": "Show report"
|
||||
},
|
||||
"showTestnetNetworks": {
|
||||
"message": "Show test networks"
|
||||
},
|
||||
@ -4442,6 +4422,10 @@
|
||||
"message": "Includes a $1% MetaMask fee.",
|
||||
"description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number."
|
||||
},
|
||||
"swapIncludesMetaMaskFeeViewAllQuotes": {
|
||||
"message": "Includes a $1% MetaMask fee – $2",
|
||||
"description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number and $2 is a link to view all quotes."
|
||||
},
|
||||
"swapLearnMore": {
|
||||
"message": "Learn more about Swaps"
|
||||
},
|
||||
@ -5206,6 +5190,9 @@
|
||||
"viewAllDetails": {
|
||||
"message": "View all details"
|
||||
},
|
||||
"viewAllQuotes": {
|
||||
"message": "view all quotes"
|
||||
},
|
||||
"viewContact": {
|
||||
"message": "View contact"
|
||||
},
|
||||
|
3
app/_locales/es/messages.json
generated
3
app/_locales/es/messages.json
generated
@ -2753,9 +2753,6 @@
|
||||
"publicAddress": {
|
||||
"message": "Dirección pública"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Cola"
|
||||
},
|
||||
"queued": {
|
||||
"message": "En cola"
|
||||
},
|
||||
|
3
app/_locales/es_419/messages.json
generated
3
app/_locales/es_419/messages.json
generated
@ -1767,9 +1767,6 @@
|
||||
"publicAddress": {
|
||||
"message": "Dirección pública"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Cola"
|
||||
},
|
||||
"queued": {
|
||||
"message": "En cola"
|
||||
},
|
||||
|
3
app/_locales/et/messages.json
generated
3
app/_locales/et/messages.json
generated
@ -550,9 +550,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Privaatvõrk"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Järjekord"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Saate selle loa tulevikus tagasi lisada, kui lähete oma kontovalikute menüüs vahelehele „Lisa luba“."
|
||||
},
|
||||
|
3
app/_locales/fa/messages.json
generated
3
app/_locales/fa/messages.json
generated
@ -560,9 +560,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "شبکه شخصی"
|
||||
},
|
||||
"queue": {
|
||||
"message": "صف"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "شما میتوانید این رمزیاب را دوباره برای آینده با رفتن به گزینه \"Add token\" در مینوی تنظیمات حساب ها، اضافه نمایید."
|
||||
},
|
||||
|
3
app/_locales/fi/messages.json
generated
3
app/_locales/fi/messages.json
generated
@ -557,9 +557,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Yksityinen verkko"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Jono"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Voit lisätä tämän tietueen myöhemmin takaisin siirtymällä tilisi vaihtoehtovalikon kohtaan ”Lisää tietue”."
|
||||
},
|
||||
|
3
app/_locales/fil/messages.json
generated
3
app/_locales/fil/messages.json
generated
@ -484,9 +484,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Pribadong Network"
|
||||
},
|
||||
"queue": {
|
||||
"message": "I-queue"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Puwede mong idagdag ulit ang token na ito sa hinaharap sa pamamagitan ng pagpunta sa “Magdagdag ng token” sa menu ng mga opsyon ng iyong mga accounts."
|
||||
},
|
||||
|
3
app/_locales/fr/messages.json
generated
3
app/_locales/fr/messages.json
generated
@ -2753,9 +2753,6 @@
|
||||
"publicAddress": {
|
||||
"message": "Adresse publique"
|
||||
},
|
||||
"queue": {
|
||||
"message": "File d’attente"
|
||||
},
|
||||
"queued": {
|
||||
"message": "En attente"
|
||||
},
|
||||
|
3
app/_locales/gu/messages.json
generated
3
app/_locales/gu/messages.json
generated
@ -103,9 +103,6 @@
|
||||
"password": {
|
||||
"message": "પાસવર્ડ"
|
||||
},
|
||||
"queue": {
|
||||
"message": "કતારમાં"
|
||||
},
|
||||
"reject": {
|
||||
"message": "નકારો"
|
||||
},
|
||||
|
3
app/_locales/he/messages.json
generated
3
app/_locales/he/messages.json
generated
@ -557,9 +557,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "רשת פרטית"
|
||||
},
|
||||
"queue": {
|
||||
"message": "תור"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "באפשרותך להוסיף טוקן זה בחזרה בעתיד על ידי מעבר אל \"הוסף טוקן\" בתפריט אפשרויות החשבונות שלך."
|
||||
},
|
||||
|
3
app/_locales/hi/messages.json
generated
3
app/_locales/hi/messages.json
generated
@ -2753,9 +2753,6 @@
|
||||
"publicAddress": {
|
||||
"message": "सार्वजनिक पता"
|
||||
},
|
||||
"queue": {
|
||||
"message": "कतार"
|
||||
},
|
||||
"queued": {
|
||||
"message": "कतारबद्ध"
|
||||
},
|
||||
|
3
app/_locales/hr/messages.json
generated
3
app/_locales/hr/messages.json
generated
@ -553,9 +553,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Privatna mreža"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Red čekanja"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Ovaj token možete dodati kasnije odlaskom pod stavku „Dodaj token” u izborniku mogućnosti računa. "
|
||||
},
|
||||
|
3
app/_locales/hu/messages.json
generated
3
app/_locales/hu/messages.json
generated
@ -553,9 +553,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Magánhálózat"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Nyomtatólista"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Ezt a tokent a jövőben is hozzáadhatja, ha a fiókbeállítások menü „Token hozzáadása” elemére lép."
|
||||
},
|
||||
|
3
app/_locales/id/messages.json
generated
3
app/_locales/id/messages.json
generated
@ -2753,9 +2753,6 @@
|
||||
"publicAddress": {
|
||||
"message": "Alamat publik"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Antrean"
|
||||
},
|
||||
"queued": {
|
||||
"message": "Antrean"
|
||||
},
|
||||
|
3
app/_locales/it/messages.json
generated
3
app/_locales/it/messages.json
generated
@ -1301,9 +1301,6 @@
|
||||
"provide": {
|
||||
"message": "Fornisci"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Coda"
|
||||
},
|
||||
"queued": {
|
||||
"message": "In coda"
|
||||
},
|
||||
|
3
app/_locales/ja/messages.json
generated
3
app/_locales/ja/messages.json
generated
@ -2753,9 +2753,6 @@
|
||||
"publicAddress": {
|
||||
"message": "パブリックアドレス"
|
||||
},
|
||||
"queue": {
|
||||
"message": "キュー"
|
||||
},
|
||||
"queued": {
|
||||
"message": "キュー待ち"
|
||||
},
|
||||
|
3
app/_locales/kn/messages.json
generated
3
app/_locales/kn/messages.json
generated
@ -560,9 +560,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "ಖಾಸಗಿ ನೆಟ್ವರ್ಕ್"
|
||||
},
|
||||
"queue": {
|
||||
"message": "ಸರತಿ"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "ನಿಮ್ಮ ಖಾತೆಗಳ ಆಯ್ಕೆಗಳ ಮೆನುವಿನಲ್ಲಿ \"ಟೋಕನ್ ಸೇರಿಸು\" ಗೆ ಹೋಗುವ ಮೂಲಕ ನೀವು ಈ ಟೋಕನ್ ಅನ್ನು ಭವಿಷ್ಯದಲ್ಲಿ ಮರಳಿ ಸೇರಿಸಬಹುದು."
|
||||
},
|
||||
|
3
app/_locales/ko/messages.json
generated
3
app/_locales/ko/messages.json
generated
@ -2753,9 +2753,6 @@
|
||||
"publicAddress": {
|
||||
"message": "공개 주소"
|
||||
},
|
||||
"queue": {
|
||||
"message": "대기열"
|
||||
},
|
||||
"queued": {
|
||||
"message": "대기열에 지정됨"
|
||||
},
|
||||
|
3
app/_locales/lt/messages.json
generated
3
app/_locales/lt/messages.json
generated
@ -560,9 +560,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Privatus tinklas"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Eilė"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Šį žetoną galite bet kada galite įtraukti ir vėl, tiesiog savo paskyros parinkčių meniu nueikite į „Įtraukti žetoną“."
|
||||
},
|
||||
|
3
app/_locales/lv/messages.json
generated
3
app/_locales/lv/messages.json
generated
@ -556,9 +556,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Privātais tīkls"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Rinda"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Jūs varat šo marķieri iestatīt atpakaļ nākotnē, konta opciju izvēlnē atverot \"Pievienot marķieri\"."
|
||||
},
|
||||
|
3
app/_locales/ml/messages.json
generated
3
app/_locales/ml/messages.json
generated
@ -103,9 +103,6 @@
|
||||
"password": {
|
||||
"message": "പാസ്വേഡ്"
|
||||
},
|
||||
"queue": {
|
||||
"message": "ക്യൂവിൽ"
|
||||
},
|
||||
"reject": {
|
||||
"message": "നിരസിക്കുക"
|
||||
},
|
||||
|
3
app/_locales/mr/messages.json
generated
3
app/_locales/mr/messages.json
generated
@ -103,9 +103,6 @@
|
||||
"password": {
|
||||
"message": "पासवर्ड"
|
||||
},
|
||||
"queue": {
|
||||
"message": "रांग"
|
||||
},
|
||||
"reject": {
|
||||
"message": "नाकारा"
|
||||
},
|
||||
|
3
app/_locales/ms/messages.json
generated
3
app/_locales/ms/messages.json
generated
@ -540,9 +540,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Rangkaian Persendirian"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Baris Gilir"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Anda boleh tambah token ini kembali pada masa depan dengan pergi ke \"Tambah token\" di dalam menu pilihan akaun anda."
|
||||
},
|
||||
|
3
app/_locales/no/messages.json
generated
3
app/_locales/no/messages.json
generated
@ -544,9 +544,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Privat nettverk "
|
||||
},
|
||||
"queue": {
|
||||
"message": "Kø"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Du kan legge til dette tokenet igjen i fremtiden ved å gå til \"Legg til token\" i menyen for kontoalternativer."
|
||||
},
|
||||
|
3
app/_locales/ph/messages.json
generated
3
app/_locales/ph/messages.json
generated
@ -1105,9 +1105,6 @@
|
||||
"publicAddress": {
|
||||
"message": "Pampublikong Address"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Queue"
|
||||
},
|
||||
"queued": {
|
||||
"message": "Naka-queue"
|
||||
},
|
||||
|
3
app/_locales/pl/messages.json
generated
3
app/_locales/pl/messages.json
generated
@ -554,9 +554,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Sieć prywatna"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Kolejka"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Możesz później ponownie dodać ten token poprzez \"Dodaj token\" w opcjach menu swojego konta."
|
||||
},
|
||||
|
3
app/_locales/pt/messages.json
generated
3
app/_locales/pt/messages.json
generated
@ -2753,9 +2753,6 @@
|
||||
"publicAddress": {
|
||||
"message": "Endereço público"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Fila"
|
||||
},
|
||||
"queued": {
|
||||
"message": "Na fila"
|
||||
},
|
||||
|
3
app/_locales/pt_BR/messages.json
generated
3
app/_locales/pt_BR/messages.json
generated
@ -1767,9 +1767,6 @@
|
||||
"publicAddress": {
|
||||
"message": "Endereço público"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Fila"
|
||||
},
|
||||
"queued": {
|
||||
"message": "Na fila"
|
||||
},
|
||||
|
3
app/_locales/pt_PT/messages.json
generated
3
app/_locales/pt_PT/messages.json
generated
@ -113,9 +113,6 @@
|
||||
"privacyMsg": {
|
||||
"message": "Política de Privacidade"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Fila"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Rejeitar"
|
||||
},
|
||||
|
3
app/_locales/ro/messages.json
generated
3
app/_locales/ro/messages.json
generated
@ -547,9 +547,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Rețea privată"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Coadă"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Puteți adăuga din nou acest indicativ în viitor accesând „Adăugați indicativ” din meniul de opțiuni al contului dvs."
|
||||
},
|
||||
|
3
app/_locales/ru/messages.json
generated
3
app/_locales/ru/messages.json
generated
@ -2753,9 +2753,6 @@
|
||||
"publicAddress": {
|
||||
"message": "Открытый адрес"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Очередь"
|
||||
},
|
||||
"queued": {
|
||||
"message": "В очереди"
|
||||
},
|
||||
|
3
app/_locales/sk/messages.json
generated
3
app/_locales/sk/messages.json
generated
@ -532,9 +532,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Soukromá síť"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Poradie"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Tento token můžete v budoucnu přidat zpět s „Přidat token“ v nastavení účtu."
|
||||
},
|
||||
|
3
app/_locales/sl/messages.json
generated
3
app/_locales/sl/messages.json
generated
@ -548,9 +548,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Zasebno omrežje"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Čakalna vrsta"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Ta žeton lahko dodate tudi kasneje z uporabo gumba “Dodaj žeton” v možnostih vašega računa."
|
||||
},
|
||||
|
3
app/_locales/sr/messages.json
generated
3
app/_locales/sr/messages.json
generated
@ -551,9 +551,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Privatna mreža"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Ред"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "U budućnosti možete vratiti ovaj token tako što ćete otvoriti „Dodaj token“ u meniju opcija vašeg naloga."
|
||||
},
|
||||
|
3
app/_locales/sv/messages.json
generated
3
app/_locales/sv/messages.json
generated
@ -544,9 +544,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Privat nätverk"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Utskriftskö"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Du kan lägga till denna token i framtiden genom att välja \"Lägg till token\" i kontots alternativmeny."
|
||||
},
|
||||
|
3
app/_locales/sw/messages.json
generated
3
app/_locales/sw/messages.json
generated
@ -538,9 +538,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Mtandao Binafsi"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Foleni"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Unaweza kuongeza tena kianzio hiki hapo baadaye kwa kwenda kwenye \"Ongeza kianzio\" kwenye machaguo yako ya menyu ya akaunti."
|
||||
},
|
||||
|
3
app/_locales/ta/messages.json
generated
3
app/_locales/ta/messages.json
generated
@ -322,9 +322,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "தனியார் நெட்வொர்க்"
|
||||
},
|
||||
"queue": {
|
||||
"message": "வரிசை"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "உங்கள் கணக்கு விருப்பங்கள் மெனுவில் \"டோக்கனைச் சேர்\" என்பதன் மூலம் நீங்கள் எதிர்காலத்தில் இந்த டோக்கனை மீண்டும் சேர்க்கலாம்."
|
||||
},
|
||||
|
3
app/_locales/te/messages.json
generated
3
app/_locales/te/messages.json
generated
@ -103,9 +103,6 @@
|
||||
"password": {
|
||||
"message": "పాస్వర్డ్"
|
||||
},
|
||||
"queue": {
|
||||
"message": "క్రమ వరుస"
|
||||
},
|
||||
"reject": {
|
||||
"message": "తిరస్కరించు"
|
||||
},
|
||||
|
3
app/_locales/tl/messages.json
generated
3
app/_locales/tl/messages.json
generated
@ -2753,9 +2753,6 @@
|
||||
"publicAddress": {
|
||||
"message": "Pampublikong Address"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Pila"
|
||||
},
|
||||
"queued": {
|
||||
"message": "Naka-queue"
|
||||
},
|
||||
|
3
app/_locales/tr/messages.json
generated
3
app/_locales/tr/messages.json
generated
@ -2753,9 +2753,6 @@
|
||||
"publicAddress": {
|
||||
"message": "Genel adres"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Kuyruğa al"
|
||||
},
|
||||
"queued": {
|
||||
"message": "Kuyruğa alındı"
|
||||
},
|
||||
|
3
app/_locales/uk/messages.json
generated
3
app/_locales/uk/messages.json
generated
@ -560,9 +560,6 @@
|
||||
"privateNetwork": {
|
||||
"message": "Приватна мережа"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Черга"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Ви можете знову додати цей токен у меню облікового запису у розділі “Додати токен”. "
|
||||
},
|
||||
|
3
app/_locales/vi/messages.json
generated
3
app/_locales/vi/messages.json
generated
@ -2753,9 +2753,6 @@
|
||||
"publicAddress": {
|
||||
"message": "Địa chỉ công khai"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Hàng đợi"
|
||||
},
|
||||
"queued": {
|
||||
"message": "Đã đưa vào hàng đợi"
|
||||
},
|
||||
|
3
app/_locales/zh_CN/messages.json
generated
3
app/_locales/zh_CN/messages.json
generated
@ -2753,9 +2753,6 @@
|
||||
"publicAddress": {
|
||||
"message": "公共地址"
|
||||
},
|
||||
"queue": {
|
||||
"message": "队列"
|
||||
},
|
||||
"queued": {
|
||||
"message": "队列中"
|
||||
},
|
||||
|
3
app/_locales/zh_TW/messages.json
generated
3
app/_locales/zh_TW/messages.json
generated
@ -1051,9 +1051,6 @@
|
||||
"publicAddress": {
|
||||
"message": "公開位址"
|
||||
},
|
||||
"queue": {
|
||||
"message": "佇列"
|
||||
},
|
||||
"queued": {
|
||||
"message": "已排入佇列"
|
||||
},
|
||||
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 240 KiB |
Binary file not shown.
Before Width: | Height: | Size: 140 KiB |
@ -1,6 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12.36 6L12.76 8H18V14H14.64L14.24 12H7V6H12.36ZM14 4H5V21H7V14H12.6L13 16H20V6H14.4L14 4Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
Before Width: | Height: | Size: 231 B |
47
app/images/gnosis.svg
Normal file
47
app/images/gnosis.svg
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1080" height="1080" viewBox="0 0 1080 1080" xml:space="preserve">
|
||||
<desc>Created with Fabric.js 5.2.4</desc>
|
||||
<defs>
|
||||
</defs>
|
||||
<g transform="matrix(1 0 0 1 540 540)" id="6d236122-bb3d-4e42-9c6b-8d2a0958998c" >
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 540 540)" id="e68d71f6-858d-496f-973e-8d82de5e6bfb" >
|
||||
<rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1; visibility: hidden;" vector-effect="non-scaling-stroke" x="-540" y="-540" rx="0" ry="0" width="1080" height="1080" />
|
||||
</g>
|
||||
<g transform="matrix(12.89 0 0 12.89 539.9 539.9)" id="fc2f5ed2-9b97-4553-a90e-ba5317325e12" >
|
||||
<circle style="stroke: rgb(0,0,0); stroke-opacity: 0; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(240,235,222); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" cx="0" cy="0" r="35" />
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 540 540)" >
|
||||
<g style="" vector-effect="non-scaling-stroke" >
|
||||
<g transform="matrix(1 0 0 1 -184.84 -28.38)" >
|
||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(19,54,41); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-215.27, -371.55)" d="M 131.685 352.096 C 131.319 328.91 138.859 306.291 153.063 287.962 L 298.861 433.76 C 280.484 447.873 257.897 455.402 234.727 455.138 C 207.433 455.025 181.289 444.133 161.989 424.833 C 142.69 405.534 131.797 379.39 131.685 352.096 Z" stroke-linecap="round" />
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 185.45 -28.86)" >
|
||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(19,54,41); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-585.54, -371.07)" d="M 565.443 455.03 C 579.094 455.087 592.621 452.441 605.245 447.247 C 617.869 442.052 629.34 434.411 638.997 424.763 C 648.655 415.116 656.308 403.652 661.516 391.034 C 666.723 378.415 669.383 364.891 669.34 351.24 C 669.596 328.071 662.068 305.487 647.962 287.106 L 501.736 433.332 C 519.913 447.55 542.367 455.198 565.443 455.03 Z" stroke-linecap="round" />
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 0 96.31)" >
|
||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(19,54,41); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-400.1, -496.24)" d="M 678.219 257.499 C 700.697 283.894 713.049 317.427 713.065 352.097 C 713.037 391.086 697.528 428.47 669.948 456.03 C 642.369 483.589 604.974 499.071 565.984 499.071 C 531.496 499.109 498.103 486.956 471.707 464.759 L 400.946 535.52 L 330.184 464.759 C 303.77 486.996 270.329 499.153 235.8 499.071 C 216.449 499.141 197.275 495.392 179.376 488.039 C 161.476 480.685 145.204 469.872 131.491 456.219 C 117.778 442.566 106.893 426.34 99.4623 408.473 C 92.0311 390.606 88.1987 371.448 88.1848 352.097 C 88.2124 317.587 100.356 284.183 122.497 257.713 L 89.4674 224.684 L 57.9353 192.616 C 19.9503 255.069 -0.0950739 326.78 0.000339029 399.877 C -0.0137127 452.416 10.3265 504.443 30.4292 552.985 C 50.532 601.526 80.0034 645.631 117.159 682.777 C 154.315 719.923 198.428 749.382 246.975 769.472 C 295.521 789.562 347.551 799.888 400.09 799.86 C 506.118 799.832 607.802 757.729 682.815 682.796 C 757.828 607.863 800.04 506.225 800.181 400.197 C 800.841 327.112 780.991 255.307 742.887 192.937 L 678.219 257.499 Z" stroke-linecap="round" />
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 0 -162.9)" >
|
||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(19,54,41); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-400.09, -237.03)" d="M 689.873 123.993 C 652.563 84.7656 607.659 53.5429 557.896 32.2262 C 508.132 10.9095 454.551 -0.0550433 400.414 0.000207773 C 346.266 -0.0259924 292.677 10.9513 242.9 32.2658 C 193.123 53.5803 148.197 84.7874 110.848 123.993 C 101.121 134.682 91.6082 145.371 82.7363 156.808 L 400.093 474.058 L 717.45 156.488 C 709.071 144.991 699.854 134.13 689.873 123.993 L 689.873 123.993 Z M 400.414 399.984 L 154.566 154.136 C 186.709 121.65 225.008 95.8997 267.221 78.3905 C 309.434 60.8813 354.714 51.9648 400.414 52.1627 C 446.121 51.9109 491.418 60.8027 533.638 78.3151 C 575.859 95.8274 614.151 121.607 646.261 154.136 L 400.414 399.984 Z" stroke-linecap="round" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="matrix(0 0 0 0 0 0)" >
|
||||
<g style="" >
|
||||
</g>
|
||||
</g>
|
||||
<g transform="matrix(0 0 0 0 0 0)" >
|
||||
<g style="" >
|
||||
</g>
|
||||
</g>
|
||||
<g transform="matrix(NaN NaN NaN NaN 0 0)" >
|
||||
<g style="" >
|
||||
</g>
|
||||
</g>
|
||||
<g transform="matrix(NaN NaN NaN NaN 0 0)" >
|
||||
<g style="" >
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.2 KiB |
1
app/images/icons/check-bold.svg
Normal file
1
app/images/icons/check-bold.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m412 145c12 12 12 32 0 45l-185 183c-12 12-32 12-45 0l-81-80c-13-13-13-33 0-46 12-12 32-12 45 0l59 58 161-161c13-12 33-12 46 1z"/></svg>
|
After Width: | Height: | Size: 207 B |
1
app/images/icons/minus-bold.svg
Normal file
1
app/images/icons/minus-bold.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m93 224l320 0c18 0 32 14 32 32 0 18-14 32-32 32l-320 0c-18 0-32-14-32-32 0-18 14-32 32-32z"/></svg>
|
After Width: | Height: | Size: 171 B |
@ -1,5 +1,5 @@
|
||||
{
|
||||
"content_security_policy": "frame-ancestors 'none'; script-src 'self'; object-src 'self'",
|
||||
"content_security_policy": "frame-ancestors 'none'; script-src 'self' 'wasm-unsafe-eval'; object-src 'self'",
|
||||
"externally_connectable": {
|
||||
"matches": ["https://metamask.io/*"],
|
||||
"ids": ["*"]
|
||||
|
@ -135,15 +135,12 @@ export default class IncomingTransactionsController {
|
||||
}
|
||||
|
||||
start() {
|
||||
const { featureFlags = {} } = this.preferencesController.store.getState();
|
||||
const { showIncomingTransactions } = featureFlags;
|
||||
const chainId = this.getCurrentChainId();
|
||||
|
||||
if (!showIncomingTransactions) {
|
||||
return;
|
||||
if (this._allowedToMakeFetchIncomingTx(chainId)) {
|
||||
this.blockTracker.removeListener('latest', this._onLatestBlock);
|
||||
this.blockTracker.addListener('latest', this._onLatestBlock);
|
||||
}
|
||||
|
||||
this.blockTracker.removeListener('latest', this._onLatestBlock);
|
||||
this.blockTracker.addListener('latest', this._onLatestBlock);
|
||||
}
|
||||
|
||||
stop() {
|
||||
@ -161,13 +158,9 @@ export default class IncomingTransactionsController {
|
||||
* @param {number} [newBlockNumberDec] - block number to begin fetching from
|
||||
*/
|
||||
async _update(address, newBlockNumberDec) {
|
||||
const { completedOnboarding } = this.onboardingController.store.getState();
|
||||
const chainId = this.getCurrentChainId();
|
||||
if (
|
||||
!Object.hasOwnProperty.call(ETHERSCAN_SUPPORTED_NETWORKS, chainId) ||
|
||||
!address ||
|
||||
!completedOnboarding
|
||||
) {
|
||||
|
||||
if (!address || !this._allowedToMakeFetchIncomingTx(chainId)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@ -302,4 +295,26 @@ export default class IncomingTransactionsController {
|
||||
type: TransactionType.incoming,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chainId - {string} The chainId of the current network
|
||||
* @returns {boolean} Whether or not the user has consented to show incoming transactions
|
||||
*/
|
||||
_allowedToMakeFetchIncomingTx(chainId) {
|
||||
const { featureFlags = {} } = this.preferencesController.store.getState();
|
||||
const { completedOnboarding } = this.onboardingController.store.getState();
|
||||
|
||||
const hasIncomingTransactionsFeatureEnabled = Boolean(
|
||||
featureFlags.showIncomingTransactions,
|
||||
);
|
||||
|
||||
const isEtherscanSupportedNetwork = Boolean(
|
||||
ETHERSCAN_SUPPORTED_NETWORKS[chainId],
|
||||
);
|
||||
return (
|
||||
completedOnboarding &&
|
||||
isEtherscanSupportedNetwork &&
|
||||
hasIncomingTransactionsFeatureEnabled
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -78,11 +78,11 @@ function getMockPreferencesController({
|
||||
};
|
||||
}
|
||||
|
||||
function getMockOnboardingController() {
|
||||
function getMockOnboardingController({ completedOnboarding = true } = {}) {
|
||||
return {
|
||||
store: {
|
||||
getState: sinon.stub().returns({
|
||||
completedOnboarding: true,
|
||||
completedOnboarding,
|
||||
}),
|
||||
subscribe: sinon.spy(),
|
||||
},
|
||||
@ -98,6 +98,16 @@ function getMockBlockTracker() {
|
||||
};
|
||||
}
|
||||
|
||||
function getDefaultControllerOpts() {
|
||||
return {
|
||||
blockTracker: getMockBlockTracker(),
|
||||
...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI),
|
||||
preferencesController: getMockPreferencesController(),
|
||||
onboardingController: getMockOnboardingController(),
|
||||
initState: getEmptyInitState(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import(
|
||||
* '../../../../app/scripts/controllers/incoming-transactions'
|
||||
@ -226,6 +236,7 @@ describe('IncomingTransactionsController', function () {
|
||||
preferencesController: getMockPreferencesController(),
|
||||
onboardingController: getMockOnboardingController(),
|
||||
initState: {},
|
||||
getCurrentChainId: () => CHAIN_IDS.GOERLI,
|
||||
},
|
||||
);
|
||||
|
||||
@ -831,6 +842,97 @@ describe('IncomingTransactionsController', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('block explorer lookup', function () {
|
||||
let sandbox;
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox = sinon.createSandbox();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
function stubFetch() {
|
||||
return sandbox.stub(window, 'fetch');
|
||||
}
|
||||
|
||||
function assertStubNotCalled(stub) {
|
||||
assert(stub.callCount === 0);
|
||||
}
|
||||
|
||||
async function triggerUpdate(incomingTransactionsController) {
|
||||
const subscription =
|
||||
incomingTransactionsController.preferencesController.store.subscribe.getCall(
|
||||
1,
|
||||
).args[0];
|
||||
|
||||
// Sets address causing a call to _update
|
||||
await subscription({ selectedAddress: MOCK_SELECTED_ADDRESS });
|
||||
}
|
||||
|
||||
it('should not happen when incoming transactions feature is disabled', async function () {
|
||||
const incomingTransactionsController = new IncomingTransactionsController(
|
||||
{
|
||||
...getDefaultControllerOpts(),
|
||||
preferencesController: getMockPreferencesController({
|
||||
showIncomingTransactions: false,
|
||||
}),
|
||||
},
|
||||
);
|
||||
const fetchStub = stubFetch();
|
||||
await triggerUpdate(incomingTransactionsController);
|
||||
assertStubNotCalled(fetchStub);
|
||||
});
|
||||
|
||||
it('should not happen when onboarding is in progress', async function () {
|
||||
const incomingTransactionsController = new IncomingTransactionsController(
|
||||
{
|
||||
...getDefaultControllerOpts(),
|
||||
onboardingController: getMockOnboardingController({
|
||||
completedOnboarding: false,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const fetchStub = stubFetch();
|
||||
await triggerUpdate(incomingTransactionsController);
|
||||
assertStubNotCalled(fetchStub);
|
||||
});
|
||||
|
||||
it('should not happen when chain id is not supported', async function () {
|
||||
const incomingTransactionsController = new IncomingTransactionsController(
|
||||
{
|
||||
...getDefaultControllerOpts(),
|
||||
getCurrentChainId: () => FAKE_CHAIN_ID,
|
||||
},
|
||||
);
|
||||
|
||||
const fetchStub = stubFetch();
|
||||
await triggerUpdate(incomingTransactionsController);
|
||||
assertStubNotCalled(fetchStub);
|
||||
});
|
||||
|
||||
it('should make api call when chain id, incoming features, and onboarding status are ok', async function () {
|
||||
const incomingTransactionsController = new IncomingTransactionsController(
|
||||
{
|
||||
...getDefaultControllerOpts(),
|
||||
getCurrentChainId: () => CHAIN_IDS.GOERLI,
|
||||
onboardingController: getMockOnboardingController({
|
||||
completedOnboarding: true,
|
||||
}),
|
||||
preferencesController: getMockPreferencesController({
|
||||
showIncomingTransactions: true,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const fetchStub = stubFetch();
|
||||
await triggerUpdate(incomingTransactionsController);
|
||||
assert(fetchStub.callCount === 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_update', function () {
|
||||
describe('when state is empty (initialized)', function () {
|
||||
it('should use provided block number and update the latest block seen', async function () {
|
||||
|
@ -425,8 +425,12 @@ export default class MetaMetricsController {
|
||||
setParticipateInMetaMetrics(participateInMetaMetrics) {
|
||||
let { metaMetricsId } = this.state;
|
||||
if (participateInMetaMetrics && !metaMetricsId) {
|
||||
// We also need to start sentry automatic session tracking at this point
|
||||
globalThis.sentry?.startSession();
|
||||
metaMetricsId = this.generateMetaMetricsId();
|
||||
} else if (participateInMetaMetrics === false) {
|
||||
// We also need to stop sentry automatic session tracking at this point
|
||||
globalThis.sentry?.endSession();
|
||||
metaMetricsId = null;
|
||||
}
|
||||
this.store.updateState({ participateInMetaMetrics, metaMetricsId });
|
||||
|
@ -146,6 +146,14 @@ describe('MetaMetricsController', function () {
|
||||
const now = new Date();
|
||||
let clock;
|
||||
beforeEach(function () {
|
||||
globalThis.sentry = {
|
||||
startSession: sinon.fake(() => {
|
||||
/** NOOP */
|
||||
}),
|
||||
endSession: sinon.fake(() => {
|
||||
/** NOOP */
|
||||
}),
|
||||
};
|
||||
clock = sinon.useFakeTimers(now.getTime());
|
||||
sinon.stub(Utils, 'generateRandomId').returns('DUMMY_RANDOM_ID');
|
||||
});
|
||||
@ -312,6 +320,7 @@ describe('MetaMetricsController', function () {
|
||||
});
|
||||
assert.equal(metaMetricsController.state.participateInMetaMetrics, null);
|
||||
metaMetricsController.setParticipateInMetaMetrics(true);
|
||||
assert.ok(globalThis.sentry.startSession.calledOnce);
|
||||
assert.equal(metaMetricsController.state.participateInMetaMetrics, true);
|
||||
metaMetricsController.setParticipateInMetaMetrics(false);
|
||||
assert.equal(metaMetricsController.state.participateInMetaMetrics, false);
|
||||
@ -328,6 +337,7 @@ describe('MetaMetricsController', function () {
|
||||
it('should nullify the metaMetricsId when set to false', function () {
|
||||
const metaMetricsController = getMetaMetricsController();
|
||||
metaMetricsController.setParticipateInMetaMetrics(false);
|
||||
assert.ok(globalThis.sentry.endSession.calledOnce);
|
||||
assert.equal(metaMetricsController.state.metaMetricsId, null);
|
||||
});
|
||||
});
|
||||
|
@ -2,10 +2,6 @@ import EventEmitter from 'events';
|
||||
import log from 'loglevel';
|
||||
import { captureException } from '@sentry/browser';
|
||||
import { isEqual } from 'lodash';
|
||||
import {
|
||||
PersonalMessageManager,
|
||||
TypedMessageManager,
|
||||
} from '@metamask/message-manager';
|
||||
import { CUSTODIAN_TYPES } from '@metamask-institutional/custody-keyring';
|
||||
import {
|
||||
updateCustodianTransactions,
|
||||
@ -48,20 +44,10 @@ export default class MMIController extends EventEmitter {
|
||||
this.metaMetricsController = opts.metaMetricsController;
|
||||
this.networkController = opts.networkController;
|
||||
this.permissionController = opts.permissionController;
|
||||
this.signatureController = opts.signatureController;
|
||||
this.platform = opts.platform;
|
||||
this.extension = opts.extension;
|
||||
|
||||
this.personalMessageManager = new PersonalMessageManager(
|
||||
undefined,
|
||||
undefined,
|
||||
this.securityProviderRequest,
|
||||
);
|
||||
this.typedMessageManager = new TypedMessageManager(
|
||||
undefined,
|
||||
undefined,
|
||||
this.securityProviderRequest,
|
||||
);
|
||||
|
||||
// Prepare event listener after transactionUpdateController gets initiated
|
||||
this.transactionUpdateController.prepareEventListener(
|
||||
this.custodianEventHandlerFactory.bind(this),
|
||||
@ -87,6 +73,20 @@ export default class MMIController extends EventEmitter {
|
||||
await this.prepareMmiPortfolio();
|
||||
}, this.preferencesController.store.getState()),
|
||||
);
|
||||
|
||||
this.signatureController.hub.on(
|
||||
'personal_sign:signed',
|
||||
async ({ signature, messageId }) => {
|
||||
await this.handleSigningEvents(signature, messageId, 'personal');
|
||||
},
|
||||
);
|
||||
|
||||
this.signatureController.hub.on(
|
||||
'eth_signTypedData:signed',
|
||||
async ({ signature, messageId }) => {
|
||||
await this.handleSigningEvents(signature, messageId, 'v4');
|
||||
},
|
||||
);
|
||||
} // End of constructor
|
||||
|
||||
async persistKeyringsAfterRefreshTokenChange() {
|
||||
@ -111,8 +111,7 @@ export default class MMIController extends EventEmitter {
|
||||
getState: () => this.getState(),
|
||||
getPendingNonce: (address) => this.getPendingNonce(address),
|
||||
setTxHash: (txId, txHash) => this.txController.setTxHash(txId, txHash),
|
||||
typedMessageManager: this.typedMessageManager,
|
||||
personalMessageManager: this.personalMessageManager,
|
||||
signatureController: this.signatureController,
|
||||
txStateManager: this.txController.txStateManager,
|
||||
custodyController: this.custodyController,
|
||||
trackTransactionEvent:
|
||||
@ -185,9 +184,10 @@ export default class MMIController extends EventEmitter {
|
||||
keyring,
|
||||
type,
|
||||
txList,
|
||||
getPendingNonce: this.getPendingNonce.bind(this),
|
||||
getPendingNonce: (address) => this.getPendingNonce(address),
|
||||
setTxHash: (txId, txHash) =>
|
||||
this.txController.setTxHash(txId, txHash),
|
||||
txStateManager: this.txController.txStateManager,
|
||||
setTxHash: this.txController.setTxHash.bind(this.txController),
|
||||
custodyController: this.custodyController,
|
||||
transactionUpdateController: this.transactionUpdateController,
|
||||
});
|
||||
@ -225,15 +225,6 @@ export default class MMIController extends EventEmitter {
|
||||
) {
|
||||
this.transactionUpdateController.getCustomerProofForAddresses(addresses);
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.institutionalFeaturesController.getComplianceProjectId()) {
|
||||
this.institutionalFeaturesController.startPolling();
|
||||
}
|
||||
} catch (e) {
|
||||
log.error('Failed to start Compliance polling');
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async connectCustodyAddresses(custodianType, custodianName, accounts) {
|
||||
@ -442,25 +433,8 @@ export default class MMIController extends EventEmitter {
|
||||
return keyring.getTransactionDeepLink(from, custodyTxId);
|
||||
}
|
||||
|
||||
async getCustodianToken(custodianType) {
|
||||
let currentCustodyType;
|
||||
|
||||
const address = this.preferencesController.getSelectedAddress();
|
||||
|
||||
if (!custodianType) {
|
||||
const resultCustody = this.custodyController.getCustodyTypeByAddress(
|
||||
toChecksumHexAddress(address),
|
||||
);
|
||||
currentCustodyType = resultCustody;
|
||||
}
|
||||
let keyring = await this.keyringController.getKeyringsByType(
|
||||
currentCustodyType || `Custody - ${custodianType}`,
|
||||
)[0];
|
||||
if (!keyring) {
|
||||
keyring = await this.keyringController.addNewKeyring(
|
||||
currentCustodyType || `Custody - ${custodianType}`,
|
||||
);
|
||||
}
|
||||
async getCustodianToken(address) {
|
||||
const keyring = await this.keyringController.getKeyringForAccount(address);
|
||||
const { authDetails } = keyring.getAccountDetails(address);
|
||||
return keyring ? authDetails.jwt || authDetails.refreshToken : '';
|
||||
}
|
||||
@ -568,8 +542,13 @@ export default class MMIController extends EventEmitter {
|
||||
const getAccountDetails = (address) =>
|
||||
this.custodyController.getAccountDetails(address);
|
||||
const extensionId = this.extension.runtime.id;
|
||||
|
||||
const { networkConfigurations: networkConfigurationsById } =
|
||||
this.networkController.state;
|
||||
const networkConfigurations = Object.values(networkConfigurationsById);
|
||||
|
||||
const networks = [
|
||||
...this.preferencesController.getRpcMethodPreferences(),
|
||||
...networkConfigurations,
|
||||
{ chainId: CHAIN_IDS.MAINNET },
|
||||
{ chainId: CHAIN_IDS.GOERLI },
|
||||
];
|
||||
@ -598,6 +577,42 @@ export default class MMIController extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
async newUnsignedMessage(msgParams, req, version) {
|
||||
const updatedMsgParams = { ...msgParams, deferSetAsSigned: true };
|
||||
|
||||
if (req.method.includes('eth_signTypedData')) {
|
||||
return await this.signatureController.newUnsignedTypedMessage(
|
||||
updatedMsgParams,
|
||||
req,
|
||||
version,
|
||||
);
|
||||
} else if (req.method.includes('personal_sign')) {
|
||||
return await this.signatureController.newUnsignedPersonalMessage(
|
||||
updatedMsgParams,
|
||||
req,
|
||||
);
|
||||
}
|
||||
return await this.signatureController.newUnsignedMessage(
|
||||
updatedMsgParams,
|
||||
req,
|
||||
);
|
||||
}
|
||||
|
||||
async handleSigningEvents(signature, messageId, signOperation) {
|
||||
if (signature.custodian_transactionId) {
|
||||
this.transactionUpdateController.addTransactionToWatchList(
|
||||
signature.custodian_transactionId,
|
||||
signature.from,
|
||||
signOperation,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
this.signatureController.setMessageMetadata(messageId, signature);
|
||||
|
||||
return this.getState();
|
||||
}
|
||||
|
||||
async setAccountAndNetwork(origin, address, chainId) {
|
||||
await this.appStateController.getUnlockPromise(true);
|
||||
const selectedAddress = this.preferencesController.getSelectedAddress();
|
||||
|
@ -2,6 +2,7 @@
|
||||
import { KeyringController } from '@metamask/eth-keyring-controller';
|
||||
import { MmiConfigurationController } from '@metamask-institutional/custody-keyring';
|
||||
import { TransactionUpdateController } from '@metamask-institutional/transaction-update';
|
||||
import { SignatureController } from '@metamask/signature-controller';
|
||||
|
||||
import MMIController from './mmi-controller';
|
||||
import TransactionController from './transactions';
|
||||
@ -33,6 +34,20 @@ describe('MMIController', function () {
|
||||
getNetworkId: jest.fn(),
|
||||
onNetworkStateChange: jest.fn(),
|
||||
}),
|
||||
signatureController: new SignatureController({
|
||||
messenger: {
|
||||
registerActionHandler: jest.fn(),
|
||||
publish: jest.fn(),
|
||||
call: jest.fn(),
|
||||
},
|
||||
keyringController: new KeyringController({
|
||||
initState: {},
|
||||
}),
|
||||
isEthSignEnabled: jest.fn(),
|
||||
getAllState: jest.fn(),
|
||||
securityProviderRequest: jest.fn(),
|
||||
getCurrentChainId: jest.fn(),
|
||||
}),
|
||||
preferencesController: new PreferencesController({
|
||||
initState: {},
|
||||
onInfuraIsBlocked: jest.fn(),
|
||||
|
@ -41,6 +41,9 @@ export default class PreferencesController {
|
||||
useNftDetection: false,
|
||||
useCurrencyRateCheck: true,
|
||||
openSeaEnabled: false,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
|
||||
securityAlertsEnabled: false,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
advancedGasFee: null,
|
||||
|
||||
// WARNING: Do not use feature flags for security-sensitive things.
|
||||
@ -185,6 +188,19 @@ export default class PreferencesController {
|
||||
});
|
||||
}
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
|
||||
/**
|
||||
* Setter for the `securityAlertsEnabled` property
|
||||
*
|
||||
* @param {boolean} securityAlertsEnabled - Whether or not the user prefers to use the security alerts.
|
||||
*/
|
||||
setSecurityAlertsEnabled(securityAlertsEnabled) {
|
||||
this.store.updateState({
|
||||
securityAlertsEnabled,
|
||||
});
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
/**
|
||||
* Setter for the `advancedGasFee` property
|
||||
*
|
||||
|
@ -315,22 +315,6 @@ export default class TransactionController extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a tx to the txlist
|
||||
*
|
||||
* @param txMeta
|
||||
* @fires ${txMeta.id}:unapproved
|
||||
*/
|
||||
addTransaction(txMeta) {
|
||||
this.txStateManager.addTransaction(txMeta);
|
||||
this.emit(`${txMeta.id}:unapproved`, txMeta);
|
||||
this._trackTransactionMetricsEvent(
|
||||
txMeta,
|
||||
TransactionMetaMetricsEvent.added,
|
||||
txMeta.actionId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wipes the transactions for a given account
|
||||
*
|
||||
@ -341,64 +325,52 @@ export default class TransactionController extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new unapproved transaction to the pipeline
|
||||
* Add a new unapproved transaction
|
||||
*
|
||||
* @returns {Promise<string>} the hash of the transaction after being submitted to the network
|
||||
* @param {object} txParams - txParams for the transaction
|
||||
* @param {object} opts - with the key origin to put the origin on the txMeta
|
||||
* @param {object} txParams - Standard parameters for an Ethereum transaction
|
||||
* @param {object} opts - Options
|
||||
* @param {string} opts.actionId - Unique ID to prevent duplicate requests
|
||||
* @param {string} opts.method - RPC method that requested the transaction
|
||||
* @param {string} opts.origin - Origin of the transaction request, such as the hostname of a dApp
|
||||
* @param {boolean} opts.requireApproval - Whether the transaction requires approval by the user
|
||||
* @param {object[]} opts.sendFlowHistory - Associated history to store with the transaction
|
||||
* @param {object} opts.swaps - Options specific to swap transactions
|
||||
* @param {boolean} opts.swaps.hasApproveTx - Whether this transaction required an approval transaction
|
||||
* @param {boolean} opts.swaps.meta - Additional metadata to store for the transaction
|
||||
* @param {TransactionType} opts.type - Type of transaction to add, such as 'cancel' or 'swap'
|
||||
* @returns {Promise<{transactionMeta: TransactionMeta, result: Promise<string>}>} An object containing the transaction metadata, and a promise that resolves to the transaction hash after being submitted to the network
|
||||
*/
|
||||
async newUnapprovedTransaction(txParams, opts = {}) {
|
||||
log.debug(
|
||||
`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`,
|
||||
);
|
||||
async addTransaction(
|
||||
txParams,
|
||||
{
|
||||
actionId,
|
||||
method,
|
||||
origin,
|
||||
requireApproval,
|
||||
sendFlowHistory,
|
||||
swaps: { hasApproveTx, meta } = {},
|
||||
type,
|
||||
} = {},
|
||||
) {
|
||||
log.debug(`MetaMaskController addTransaction ${JSON.stringify(txParams)}`);
|
||||
|
||||
const { txMeta: initialTxMeta, isExisting } = await this._createTransaction(
|
||||
opts.method,
|
||||
txParams,
|
||||
opts.origin,
|
||||
undefined,
|
||||
undefined,
|
||||
opts.id,
|
||||
);
|
||||
const { txMeta, isExisting } = await this._createTransaction(txParams, {
|
||||
actionId,
|
||||
method,
|
||||
origin,
|
||||
sendFlowHistory,
|
||||
swaps: { hasApproveTx, meta },
|
||||
type,
|
||||
});
|
||||
|
||||
const txId = initialTxMeta.id;
|
||||
const isCompleted = this._isTransactionCompleted(initialTxMeta);
|
||||
|
||||
const finishedPromise = isCompleted
|
||||
? Promise.resolve(initialTxMeta)
|
||||
: this._waitForTransactionFinished(txId);
|
||||
|
||||
if (!isExisting && !isCompleted) {
|
||||
try {
|
||||
await this._requestTransactionApproval(initialTxMeta);
|
||||
} catch (error) {
|
||||
// Errors generated from final status using finished event
|
||||
}
|
||||
}
|
||||
|
||||
const finalTxMeta = await finishedPromise;
|
||||
const finalStatus = finalTxMeta?.status;
|
||||
|
||||
switch (finalStatus) {
|
||||
case TransactionStatus.submitted:
|
||||
return finalTxMeta.hash;
|
||||
case TransactionStatus.rejected:
|
||||
throw cleanErrorStack(
|
||||
ethErrors.provider.userRejectedRequest(
|
||||
'MetaMask Tx Signature: User denied transaction signature.',
|
||||
),
|
||||
);
|
||||
case TransactionStatus.failed:
|
||||
throw cleanErrorStack(ethErrors.rpc.internal(finalTxMeta.err.message));
|
||||
default:
|
||||
throw cleanErrorStack(
|
||||
ethErrors.rpc.internal(
|
||||
`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(
|
||||
finalTxMeta?.txParams,
|
||||
)}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
return {
|
||||
transactionMeta: txMeta,
|
||||
result: this._processApproval(txMeta, {
|
||||
isExisting,
|
||||
requireApproval,
|
||||
actionId,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -585,54 +557,6 @@ export default class TransactionController extends EventEmitter {
|
||||
return this._getTransaction(txId);
|
||||
}
|
||||
|
||||
/**
|
||||
* updates the estimate base fees of the transaction with id if the transaction state is unapproved
|
||||
*
|
||||
* @param {string} txId - transaction id
|
||||
* @param {object} txEstimateBaseFees - holds the estimate base fees parameters
|
||||
* @param {string} txEstimateBaseFees.estimatedBaseFee
|
||||
* @param {string} txEstimateBaseFees.decEstimatedBaseFee
|
||||
* @returns {TransactionMeta} the txMeta of the updated transaction
|
||||
*/
|
||||
updateTransactionEstimatedBaseFee(
|
||||
txId,
|
||||
{ estimatedBaseFee, decEstimatedBaseFee },
|
||||
) {
|
||||
this._throwErrorIfNotUnapprovedTx(
|
||||
txId,
|
||||
'updateTransactionEstimatedBaseFee',
|
||||
);
|
||||
|
||||
let txEstimateBaseFees = { estimatedBaseFee, decEstimatedBaseFee };
|
||||
// only update what is defined
|
||||
txEstimateBaseFees = pickBy(txEstimateBaseFees);
|
||||
|
||||
const note = `Update Transaction Estimated Base Fees for ${txId}`;
|
||||
this._updateTransaction(txId, txEstimateBaseFees, note);
|
||||
return this._getTransaction(txId);
|
||||
}
|
||||
|
||||
/**
|
||||
* updates a transaction's user settings only if the transaction state is unapproved
|
||||
*
|
||||
* @param {string} txId
|
||||
* @param {object} userSettings - holds the metadata
|
||||
* @param {string} userSettings.userEditedGasLimit
|
||||
* @param {string} userSettings.userFeeLevel
|
||||
* @returns {TransactionMeta} the txMeta of the updated transaction
|
||||
*/
|
||||
updateTransactionUserSettings(txId, { userEditedGasLimit, userFeeLevel }) {
|
||||
this._throwErrorIfNotUnapprovedTx(txId, 'updateTransactionUserSettings');
|
||||
|
||||
let userSettings = { userEditedGasLimit, userFeeLevel };
|
||||
// only update what is defined
|
||||
userSettings = pickBy(userSettings);
|
||||
|
||||
const note = `Update User Settings for ${txId}`;
|
||||
this._updateTransaction(txId, userSettings, note);
|
||||
return this._getTransaction(txId);
|
||||
}
|
||||
|
||||
/**
|
||||
* append new sendFlowHistory to the transaction with id if the transaction
|
||||
* state is unapproved. Returns the updated transaction.
|
||||
@ -694,7 +618,7 @@ export default class TransactionController extends EventEmitter {
|
||||
updateTxMeta.loadingDefaults = false;
|
||||
|
||||
// The history note used here 'Added new unapproved transaction.' is confusing update call only updated the gas defaults.
|
||||
// We need to improve `this.addTransaction` to accept history note and change note here.
|
||||
// We need to improve `this._addTransaction` to accept history note and change note here.
|
||||
this.txStateManager.updateTransaction(
|
||||
updateTxMeta,
|
||||
'Added new unapproved transaction.',
|
||||
@ -705,58 +629,6 @@ export default class TransactionController extends EventEmitter {
|
||||
|
||||
// ====================================================================================================================================================
|
||||
|
||||
/**
|
||||
* Validates and generates a txMeta with defaults and puts it in txStateManager
|
||||
* store.
|
||||
*
|
||||
* actionId is used to uniquely identify a request to create a transaction.
|
||||
* Only 1 transaction will be created for multiple requests with same actionId.
|
||||
* actionId is fix used for making this action idempotent to deal with scenario when
|
||||
* action is invoked multiple times with same parameters in MV3 due to service worker re-activation.
|
||||
*
|
||||
* @param txMethodType
|
||||
* @param txParams
|
||||
* @param origin
|
||||
* @param transactionType
|
||||
* @param sendFlowHistory
|
||||
* @param actionId
|
||||
* @param options
|
||||
*/
|
||||
async addUnapprovedTransaction(
|
||||
txMethodType,
|
||||
txParams,
|
||||
origin,
|
||||
transactionType,
|
||||
sendFlowHistory = [],
|
||||
actionId,
|
||||
options,
|
||||
) {
|
||||
const { txMeta, isExisting } = await this._createTransaction(
|
||||
txMethodType,
|
||||
txParams,
|
||||
origin,
|
||||
transactionType,
|
||||
sendFlowHistory,
|
||||
actionId,
|
||||
options,
|
||||
);
|
||||
if (isExisting) {
|
||||
const isCompleted = this._isTransactionCompleted(txMeta);
|
||||
|
||||
return isCompleted
|
||||
? txMeta
|
||||
: await this._waitForTransactionFinished(txMeta.id);
|
||||
}
|
||||
|
||||
if (options?.requireApproval === false) {
|
||||
await this._updateAndApproveTransaction(txMeta, actionId);
|
||||
} else {
|
||||
await this._requestTransactionApproval(txMeta, { actionId });
|
||||
}
|
||||
|
||||
return txMeta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the tx gas defaults: gas && gasPrice
|
||||
*
|
||||
@ -1122,7 +994,7 @@ export default class TransactionController extends EventEmitter {
|
||||
newTxMeta.estimatedBaseFee = estimatedBaseFee;
|
||||
}
|
||||
|
||||
this.addTransaction(newTxMeta);
|
||||
this._addTransaction(newTxMeta);
|
||||
await this._approveTransaction(newTxMeta.id, actionId, {
|
||||
hasApprovalRequest: false,
|
||||
});
|
||||
@ -1182,7 +1054,7 @@ export default class TransactionController extends EventEmitter {
|
||||
newTxMeta.estimatedBaseFee = estimatedBaseFee;
|
||||
}
|
||||
|
||||
this.addTransaction(newTxMeta);
|
||||
this._addTransaction(newTxMeta);
|
||||
await this._approveTransaction(newTxMeta.id, actionId);
|
||||
return newTxMeta;
|
||||
}
|
||||
@ -1593,21 +1465,14 @@ export default class TransactionController extends EventEmitter {
|
||||
}
|
||||
|
||||
async _createTransaction(
|
||||
txMethodType,
|
||||
txParams,
|
||||
origin,
|
||||
transactionType,
|
||||
sendFlowHistory = [],
|
||||
actionId,
|
||||
options,
|
||||
{ actionId, method, origin, sendFlowHistory = [], swaps, type },
|
||||
) {
|
||||
if (
|
||||
transactionType !== undefined &&
|
||||
!VALID_UNAPPROVED_TRANSACTION_TYPES.includes(transactionType)
|
||||
type !== undefined &&
|
||||
!VALID_UNAPPROVED_TRANSACTION_TYPES.includes(type)
|
||||
) {
|
||||
throw new Error(
|
||||
`TransactionController - invalid transactionType value: ${transactionType}`,
|
||||
);
|
||||
throw new Error(`TransactionController - invalid type value: ${type}`);
|
||||
}
|
||||
|
||||
// If a transaction is found with the same actionId, do not create a new speed-up transaction.
|
||||
@ -1665,40 +1530,32 @@ export default class TransactionController extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
const { type } = await determineTransactionType(
|
||||
const { type: determinedType } = await determineTransactionType(
|
||||
normalizedTxParams,
|
||||
this.query,
|
||||
);
|
||||
txMeta.type = transactionType || type;
|
||||
txMeta.type = type || determinedType;
|
||||
|
||||
// ensure value
|
||||
txMeta.txParams.value = txMeta.txParams.value
|
||||
? addHexPrefix(txMeta.txParams.value)
|
||||
: '0x0';
|
||||
|
||||
if (txMethodType && this.securityProviderRequest) {
|
||||
if (method && this.securityProviderRequest) {
|
||||
const securityProviderResponse = await this.securityProviderRequest(
|
||||
txMeta,
|
||||
txMethodType,
|
||||
method,
|
||||
);
|
||||
|
||||
txMeta.securityProviderResponse = securityProviderResponse;
|
||||
}
|
||||
|
||||
this.addTransaction(txMeta);
|
||||
this._addTransaction(txMeta);
|
||||
|
||||
txMeta = await this.addTransactionGasDefaults(txMeta);
|
||||
|
||||
if (
|
||||
[TransactionType.swap, TransactionType.swapApproval].includes(
|
||||
transactionType,
|
||||
)
|
||||
) {
|
||||
txMeta = await this._createSwapsTransaction(
|
||||
options?.swaps,
|
||||
transactionType,
|
||||
txMeta,
|
||||
);
|
||||
if ([TransactionType.swap, TransactionType.swapApproval].includes(type)) {
|
||||
txMeta = await this._createSwapsTransaction(swaps, type, txMeta);
|
||||
}
|
||||
|
||||
return { txMeta, isExisting: false };
|
||||
@ -1830,6 +1687,51 @@ export default class TransactionController extends EventEmitter {
|
||||
await this._approveTransaction(txMeta.id, actionId);
|
||||
}
|
||||
|
||||
async _processApproval(txMeta, { actionId, isExisting, requireApproval }) {
|
||||
const txId = txMeta.id;
|
||||
const isCompleted = this._isTransactionCompleted(txMeta);
|
||||
|
||||
const finishedPromise = isCompleted
|
||||
? Promise.resolve(txMeta)
|
||||
: this._waitForTransactionFinished(txId);
|
||||
|
||||
if (!isExisting && !isCompleted) {
|
||||
try {
|
||||
if (requireApproval === false) {
|
||||
await this._updateAndApproveTransaction(txMeta, actionId);
|
||||
} else {
|
||||
await this._requestTransactionApproval(txMeta, { actionId });
|
||||
}
|
||||
} catch (error) {
|
||||
// Errors generated from final status using finished event
|
||||
}
|
||||
}
|
||||
|
||||
const finalTxMeta = await finishedPromise;
|
||||
const finalStatus = finalTxMeta?.status;
|
||||
|
||||
switch (finalStatus) {
|
||||
case TransactionStatus.submitted:
|
||||
return finalTxMeta.hash;
|
||||
case TransactionStatus.rejected:
|
||||
throw cleanErrorStack(
|
||||
ethErrors.provider.userRejectedRequest(
|
||||
'MetaMask Tx Signature: User denied transaction signature.',
|
||||
),
|
||||
);
|
||||
case TransactionStatus.failed:
|
||||
throw cleanErrorStack(ethErrors.rpc.internal(finalTxMeta.err.message));
|
||||
default:
|
||||
throw cleanErrorStack(
|
||||
ethErrors.rpc.internal(
|
||||
`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(
|
||||
finalTxMeta?.txParams,
|
||||
)}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the tx status to approved
|
||||
* auto fills the nonce
|
||||
@ -2762,6 +2664,22 @@ export default class TransactionController extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a tx to the txlist
|
||||
*
|
||||
* @param txMeta
|
||||
* @fires ${txMeta.id}:unapproved
|
||||
*/
|
||||
_addTransaction(txMeta) {
|
||||
this.txStateManager.addTransaction(txMeta);
|
||||
this.emit(`${txMeta.id}:unapproved`, txMeta);
|
||||
this._trackTransactionMetricsEvent(
|
||||
txMeta,
|
||||
TransactionMetaMetricsEvent.added,
|
||||
txMeta.actionId,
|
||||
);
|
||||
}
|
||||
|
||||
// Approvals
|
||||
|
||||
async _requestTransactionApproval(
|
||||
|
File diff suppressed because it is too large
Load Diff
93
app/scripts/lib/ppom/indexed-db-backend.test.ts
Normal file
93
app/scripts/lib/ppom/indexed-db-backend.test.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import 'fake-indexeddb/auto';
|
||||
|
||||
import { IndexedDBPPOMStorage } from './indexed-db-backend';
|
||||
|
||||
Object.defineProperty(globalThis, 'crypto', {
|
||||
value: {
|
||||
subtle: {
|
||||
digest: () => new ArrayBuffer(12),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const enc = new TextEncoder();
|
||||
const dec = new TextDecoder('utf-8');
|
||||
|
||||
describe('IndexedDBPPOMStorage', () => {
|
||||
it('should be able to initialise correctly', () => {
|
||||
const indexDBBackend = new IndexedDBPPOMStorage('PPOMDB', 1);
|
||||
expect(indexDBBackend).toBeDefined();
|
||||
});
|
||||
|
||||
it('should be able to write and read file data if checksum matches', async () => {
|
||||
const indexDBBackend = new IndexedDBPPOMStorage('PPOMDB', 1);
|
||||
await indexDBBackend.write(
|
||||
{ name: 'fake_name', chainId: '5' },
|
||||
enc.encode('fake_data'),
|
||||
'000000000000000000000000',
|
||||
);
|
||||
const file = await indexDBBackend.read(
|
||||
{ name: 'fake_name', chainId: '5' },
|
||||
'000000000000000000000000',
|
||||
);
|
||||
expect(dec.decode(file)).toStrictEqual('fake_data');
|
||||
});
|
||||
|
||||
it('should fail to write if checksum does not match', async () => {
|
||||
const indexDBBackend = new IndexedDBPPOMStorage('PPOMDB', 1);
|
||||
await expect(async () => {
|
||||
await indexDBBackend.write(
|
||||
{ name: 'fake_name', chainId: '5' },
|
||||
enc.encode('fake_data'),
|
||||
'XXX',
|
||||
);
|
||||
}).rejects.toThrow('Checksum mismatch');
|
||||
});
|
||||
|
||||
it('should fail to read if checksum does not match', async () => {
|
||||
const indexDBBackend = new IndexedDBPPOMStorage('PPOMDB', 1);
|
||||
await expect(async () => {
|
||||
await indexDBBackend.write(
|
||||
{ name: 'fake_name', chainId: '5' },
|
||||
enc.encode('fake_data'),
|
||||
'000000000000000000000000',
|
||||
);
|
||||
await indexDBBackend.read({ name: 'fake_name', chainId: '5' }, 'XXX');
|
||||
}).rejects.toThrow('Checksum mismatch');
|
||||
});
|
||||
|
||||
it('should delete a file when delete method is called', async () => {
|
||||
const indexDBBackend = new IndexedDBPPOMStorage('PPOMDB', 1);
|
||||
await indexDBBackend.write(
|
||||
{ name: 'fake_name', chainId: '5' },
|
||||
enc.encode('fake_data'),
|
||||
'000000000000000000000000',
|
||||
);
|
||||
await indexDBBackend.delete({ name: 'fake_name', chainId: '5' });
|
||||
const result = await indexDBBackend.read(
|
||||
{ name: 'fake_name', chainId: '5' },
|
||||
'000000000000000000000000',
|
||||
);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should list all keys when dir is called', async () => {
|
||||
const keys = [
|
||||
{ chainId: '5', name: 'fake_name_1' },
|
||||
{ chainId: '1', name: 'fake_name_2' },
|
||||
];
|
||||
const indexDBBackend = new IndexedDBPPOMStorage('PPOMDB', 1);
|
||||
await indexDBBackend.write(
|
||||
keys[0],
|
||||
enc.encode('fake_data_1'),
|
||||
'000000000000000000000000',
|
||||
);
|
||||
await indexDBBackend.write(
|
||||
keys[1],
|
||||
enc.encode('fake_data_2'),
|
||||
'000000000000000000000000',
|
||||
);
|
||||
const result = await indexDBBackend.dir();
|
||||
expect(result).toStrictEqual(keys);
|
||||
});
|
||||
});
|
127
app/scripts/lib/ppom/indexed-db-backend.ts
Normal file
127
app/scripts/lib/ppom/indexed-db-backend.ts
Normal file
@ -0,0 +1,127 @@
|
||||
import { StorageBackend } from '@metamask/ppom-validator';
|
||||
|
||||
type StorageKey = {
|
||||
name: string;
|
||||
chainId: string;
|
||||
};
|
||||
|
||||
const validateChecksum = async (
|
||||
key: StorageKey,
|
||||
data: ArrayBuffer,
|
||||
checksum: string,
|
||||
) => {
|
||||
const hash = await crypto.subtle.digest('SHA-256', data);
|
||||
const hashString = Array.from(new Uint8Array(hash))
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
|
||||
if (hashString !== checksum) {
|
||||
throw new Error(`Checksum mismatch for key ${key}`);
|
||||
}
|
||||
};
|
||||
|
||||
export class IndexedDBPPOMStorage implements StorageBackend {
|
||||
private storeName: string;
|
||||
|
||||
private dbVersion: number;
|
||||
|
||||
constructor(storeName: string, dbVersion: number) {
|
||||
this.storeName = storeName;
|
||||
this.dbVersion = dbVersion;
|
||||
}
|
||||
|
||||
#getObjectStore(mode: IDBTransactionMode): Promise<IDBObjectStore> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(this.storeName, this.dbVersion);
|
||||
|
||||
request.onerror = (event: Event) => {
|
||||
reject(
|
||||
new Error(
|
||||
`Failed to open database ${this.storeName}: ${
|
||||
(event.target as any)?.error
|
||||
}`,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = (event.target as IDBOpenDBRequest).result;
|
||||
|
||||
if (!db.objectStoreNames.contains(this.storeName)) {
|
||||
db.createObjectStore(this.storeName, {
|
||||
keyPath: ['name', 'chainId'],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
request.onsuccess = (event) => {
|
||||
const db = (event.target as IDBOpenDBRequest).result;
|
||||
const transaction = db.transaction([this.storeName], mode);
|
||||
const objectStore = transaction.objectStore(this.storeName);
|
||||
resolve(objectStore);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private async objectStoreAction(
|
||||
method: 'get' | 'delete' | 'put' | 'getAllKeys',
|
||||
args?: any,
|
||||
mode: IDBTransactionMode = 'readonly',
|
||||
): Promise<any> {
|
||||
return new Promise<Event>((resolve, reject) => {
|
||||
this.#getObjectStore(mode)
|
||||
.then((objectStore) => {
|
||||
const request = objectStore[method](args);
|
||||
|
||||
request.onsuccess = async (event) => {
|
||||
resolve(event);
|
||||
};
|
||||
|
||||
request.onerror = (event) => {
|
||||
reject(
|
||||
new Error(
|
||||
`Error in indexDB operation ${method}: ${
|
||||
(event.target as any)?.error
|
||||
}`,
|
||||
),
|
||||
);
|
||||
};
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async read(key: StorageKey, checksum: string): Promise<ArrayBuffer> {
|
||||
const event = await this.objectStoreAction('get', [key.name, key.chainId]);
|
||||
const data = (event.target as any)?.result?.data;
|
||||
await validateChecksum(key, data, checksum);
|
||||
return data;
|
||||
}
|
||||
|
||||
async write(
|
||||
key: StorageKey,
|
||||
data: ArrayBuffer,
|
||||
checksum: string,
|
||||
): Promise<void> {
|
||||
await validateChecksum(key, data, checksum);
|
||||
await this.objectStoreAction('put', { ...key, data }, 'readwrite');
|
||||
}
|
||||
|
||||
async delete(key: StorageKey): Promise<void> {
|
||||
await this.objectStoreAction(
|
||||
'delete',
|
||||
[key.name, key.chainId],
|
||||
'readwrite',
|
||||
);
|
||||
}
|
||||
|
||||
async dir(): Promise<StorageKey[]> {
|
||||
const event = await this.objectStoreAction('getAllKeys');
|
||||
return (event.target as any)?.result.map(([name, chainId]: string[]) => ({
|
||||
name,
|
||||
chainId,
|
||||
}));
|
||||
}
|
||||
}
|
110
app/scripts/lib/ppom/ppom-middleware.test.ts
Normal file
110
app/scripts/lib/ppom/ppom-middleware.test.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { createPPOMMiddleware } from './ppom-middleware';
|
||||
|
||||
Object.defineProperty(globalThis, 'fetch', {
|
||||
writable: true,
|
||||
value: () => undefined,
|
||||
});
|
||||
|
||||
Object.defineProperty(globalThis, 'performance', {
|
||||
writable: true,
|
||||
value: () => undefined,
|
||||
});
|
||||
|
||||
describe('PPOMMiddleware', () => {
|
||||
it('should call ppomController.usePPOM for requests of type confirmation', async () => {
|
||||
const useMock = jest.fn();
|
||||
const controller = {
|
||||
usePPOM: useMock,
|
||||
};
|
||||
const middlewareFunction = createPPOMMiddleware(controller as any);
|
||||
await middlewareFunction(
|
||||
{ method: 'eth_sendTransaction' },
|
||||
undefined,
|
||||
() => undefined,
|
||||
);
|
||||
expect(useMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should add validation response on confirmation requests', async () => {
|
||||
const controller = {
|
||||
usePPOM: async () => Promise.resolve('VALIDATION_RESULT'),
|
||||
};
|
||||
const middlewareFunction = createPPOMMiddleware(controller as any);
|
||||
const req = { method: 'eth_sendTransaction', ppomResponse: undefined };
|
||||
await middlewareFunction(req, undefined, () => undefined);
|
||||
expect(req.ppomResponse).toBeDefined();
|
||||
});
|
||||
|
||||
it('should call next method when ppomController.usePPOM completes', async () => {
|
||||
const ppom = {
|
||||
validateJsonRpc: () => undefined,
|
||||
};
|
||||
const controller = {
|
||||
usePPOM: async (callback: any) => {
|
||||
callback(ppom);
|
||||
},
|
||||
};
|
||||
const middlewareFunction = createPPOMMiddleware(controller as any);
|
||||
const nextMock = jest.fn();
|
||||
await middlewareFunction(
|
||||
{ method: 'eth_sendTransaction' },
|
||||
undefined,
|
||||
nextMock,
|
||||
);
|
||||
expect(nextMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should call next method when ppomController.usePPOM throws error', async () => {
|
||||
const controller = {
|
||||
usePPOM: async (_callback: any) => {
|
||||
throw Error('Some error');
|
||||
},
|
||||
};
|
||||
const middlewareFunction = createPPOMMiddleware(controller as any);
|
||||
const nextMock = jest.fn();
|
||||
await middlewareFunction(
|
||||
{ method: 'eth_sendTransaction' },
|
||||
undefined,
|
||||
nextMock,
|
||||
);
|
||||
expect(nextMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should call ppom.validateJsonRpc when invoked', async () => {
|
||||
const validateMock = jest.fn();
|
||||
const ppom = {
|
||||
validateJsonRpc: validateMock,
|
||||
};
|
||||
const controller = {
|
||||
usePPOM: async (callback: any) => {
|
||||
callback(ppom);
|
||||
},
|
||||
};
|
||||
const middlewareFunction = createPPOMMiddleware(controller as any);
|
||||
await middlewareFunction(
|
||||
{ method: 'eth_sendTransaction' },
|
||||
undefined,
|
||||
() => undefined,
|
||||
);
|
||||
expect(validateMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not call ppom.validateJsonRpc when request is not for confirmation method', async () => {
|
||||
const validateMock = jest.fn();
|
||||
const ppom = {
|
||||
validateJsonRpc: validateMock,
|
||||
};
|
||||
const controller = {
|
||||
usePPOM: async (callback: any) => {
|
||||
callback(ppom);
|
||||
},
|
||||
};
|
||||
const middlewareFunction = createPPOMMiddleware(controller as any);
|
||||
await middlewareFunction(
|
||||
{ method: 'eth_someRequest' },
|
||||
undefined,
|
||||
() => undefined,
|
||||
);
|
||||
expect(validateMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
43
app/scripts/lib/ppom/ppom-middleware.ts
Normal file
43
app/scripts/lib/ppom/ppom-middleware.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { PPOM } from '@blockaid/ppom';
|
||||
|
||||
import { PPOMController } from '@metamask/ppom-validator';
|
||||
|
||||
const ConfirmationMethods = Object.freeze([
|
||||
'eth_sendRawTransaction',
|
||||
'eth_sendTransaction',
|
||||
'eth_sign',
|
||||
'eth_signTypedData',
|
||||
'eth_signTypedData_v1',
|
||||
'eth_signTypedData_v3',
|
||||
'eth_signTypedData_v4',
|
||||
'personal_sign',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Middleware function that handles JSON RPC requests.
|
||||
* This function will be called for every JSON RPC request.
|
||||
* It will call the PPOM to check if the request is malicious or benign.
|
||||
* If the request is benign, it will be forwarded to the next middleware.
|
||||
* If the request is malicious or warning, it will trigger the PPOM alert dialog,
|
||||
* after the user has confirmed or rejected the request,
|
||||
* the request will be forwarded to the next middleware, together with the PPOM response.
|
||||
*
|
||||
* @param ppomController - Instance of PPOMController.
|
||||
* @returns PPOMMiddleware function.
|
||||
*/
|
||||
export function createPPOMMiddleware(ppomController: PPOMController) {
|
||||
return async (req: any, _res: any, next: () => void) => {
|
||||
try {
|
||||
if (ConfirmationMethods.includes(req.method)) {
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
req.ppomResponse = await ppomController.usePPOM(async (ppom: PPOM) => {
|
||||
return ppom.validateJsonRpc(req);
|
||||
});
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.error('Error validating JSON RPC using PPOM: ', error);
|
||||
} finally {
|
||||
next();
|
||||
}
|
||||
};
|
||||
}
|
573
app/scripts/lib/ppom/ppom.js
Normal file
573
app/scripts/lib/ppom/ppom.js
Normal file
@ -0,0 +1,573 @@
|
||||
/* eslint-disable */
|
||||
|
||||
let wasm;
|
||||
|
||||
const heap = new Array(128).fill(undefined);
|
||||
|
||||
heap.push(undefined, null, true, false);
|
||||
|
||||
function getObject(idx) {
|
||||
return heap[idx];
|
||||
}
|
||||
|
||||
let heap_next = heap.length;
|
||||
|
||||
function dropObject(idx) {
|
||||
if (idx < 132) return;
|
||||
heap[idx] = heap_next;
|
||||
heap_next = idx;
|
||||
}
|
||||
|
||||
function takeObject(idx) {
|
||||
const ret = getObject(idx);
|
||||
dropObject(idx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
let cachedUint8Memory0 = null;
|
||||
|
||||
function getUint8Memory0() {
|
||||
if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {
|
||||
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedUint8Memory0;
|
||||
}
|
||||
|
||||
const cachedTextEncoder =
|
||||
typeof TextEncoder !== 'undefined'
|
||||
? new TextEncoder('utf-8')
|
||||
: {
|
||||
encode: () => {
|
||||
throw Error('TextEncoder not available');
|
||||
},
|
||||
};
|
||||
|
||||
const encodeString =
|
||||
typeof cachedTextEncoder.encodeInto === 'function'
|
||||
? function (arg, view) {
|
||||
return cachedTextEncoder.encodeInto(arg, view);
|
||||
}
|
||||
: function (arg, view) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
view.set(buf);
|
||||
return {
|
||||
read: arg.length,
|
||||
written: buf.length,
|
||||
};
|
||||
};
|
||||
|
||||
function passStringToWasm0(arg, malloc, realloc) {
|
||||
if (realloc === undefined) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
const ptr = malloc(buf.length, 1) >>> 0;
|
||||
getUint8Memory0()
|
||||
.subarray(ptr, ptr + buf.length)
|
||||
.set(buf);
|
||||
WASM_VECTOR_LEN = buf.length;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let len = arg.length;
|
||||
let ptr = malloc(len, 1) >>> 0;
|
||||
|
||||
const mem = getUint8Memory0();
|
||||
|
||||
let offset = 0;
|
||||
|
||||
for (; offset < len; offset++) {
|
||||
const code = arg.charCodeAt(offset);
|
||||
if (code > 0x7f) break;
|
||||
mem[ptr + offset] = code;
|
||||
}
|
||||
|
||||
if (offset !== len) {
|
||||
if (offset !== 0) {
|
||||
arg = arg.slice(offset);
|
||||
}
|
||||
ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0;
|
||||
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
|
||||
const ret = encodeString(arg, view);
|
||||
|
||||
offset += ret.written;
|
||||
}
|
||||
|
||||
WASM_VECTOR_LEN = offset;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
function isLikeNone(x) {
|
||||
return x === undefined || x === null;
|
||||
}
|
||||
|
||||
let cachedInt32Memory0 = null;
|
||||
|
||||
function getInt32Memory0() {
|
||||
if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {
|
||||
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedInt32Memory0;
|
||||
}
|
||||
|
||||
const cachedTextDecoder =
|
||||
typeof TextDecoder !== 'undefined'
|
||||
? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true })
|
||||
: {
|
||||
decode: () => {
|
||||
throw Error('TextDecoder not available');
|
||||
},
|
||||
};
|
||||
|
||||
if (typeof TextDecoder !== 'undefined') {
|
||||
cachedTextDecoder.decode();
|
||||
}
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
function addHeapObject(obj) {
|
||||
if (heap_next === heap.length) heap.push(heap.length + 1);
|
||||
const idx = heap_next;
|
||||
heap_next = heap[idx];
|
||||
|
||||
heap[idx] = obj;
|
||||
return idx;
|
||||
}
|
||||
|
||||
function debugString(val) {
|
||||
// primitive types
|
||||
const type = typeof val;
|
||||
if (type == 'number' || type == 'boolean' || val == null) {
|
||||
return `${val}`;
|
||||
}
|
||||
if (type == 'string') {
|
||||
return `"${val}"`;
|
||||
}
|
||||
if (type == 'symbol') {
|
||||
const description = val.description;
|
||||
if (description == null) {
|
||||
return 'Symbol';
|
||||
} else {
|
||||
return `Symbol(${description})`;
|
||||
}
|
||||
}
|
||||
if (type == 'function') {
|
||||
const name = val.name;
|
||||
if (typeof name == 'string' && name.length > 0) {
|
||||
return `Function(${name})`;
|
||||
} else {
|
||||
return 'Function';
|
||||
}
|
||||
}
|
||||
// objects
|
||||
if (Array.isArray(val)) {
|
||||
const length = val.length;
|
||||
let debug = '[';
|
||||
if (length > 0) {
|
||||
debug += debugString(val[0]);
|
||||
}
|
||||
for (let i = 1; i < length; i++) {
|
||||
debug += ', ' + debugString(val[i]);
|
||||
}
|
||||
debug += ']';
|
||||
return debug;
|
||||
}
|
||||
// Test for built-in
|
||||
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
|
||||
let className;
|
||||
if (builtInMatches.length > 1) {
|
||||
className = builtInMatches[1];
|
||||
} else {
|
||||
// Failed to match the standard '[object ClassName]'
|
||||
return toString.call(val);
|
||||
}
|
||||
if (className == 'Object') {
|
||||
// we're a user defined class or Object
|
||||
// JSON.stringify avoids problems with cycles, and is generally much
|
||||
// easier than looping through ownProperties of `val`.
|
||||
try {
|
||||
return 'Object(' + JSON.stringify(val) + ')';
|
||||
} catch (_) {
|
||||
return 'Object';
|
||||
}
|
||||
}
|
||||
// errors
|
||||
if (val instanceof Error) {
|
||||
return `${val.name}: ${val.message}\n${val.stack}`;
|
||||
}
|
||||
// TODO we could test for more things here, like `Set`s and `Map`s.
|
||||
return className;
|
||||
}
|
||||
|
||||
function makeMutClosure(arg0, arg1, dtor, f) {
|
||||
const state = { a: arg0, b: arg1, cnt: 1 };
|
||||
const real = (...args) => {
|
||||
// First up with a closure we increment the internal reference
|
||||
// count. This ensures that the Rust closure environment won't
|
||||
// be deallocated while we're invoking it.
|
||||
state.cnt++;
|
||||
const a = state.a;
|
||||
state.a = 0;
|
||||
try {
|
||||
return f(a, state.b, ...args);
|
||||
} finally {
|
||||
if (--state.cnt === 0) {
|
||||
dtor(a, state.b);
|
||||
} else {
|
||||
state.a = a;
|
||||
}
|
||||
}
|
||||
};
|
||||
real.original = state;
|
||||
|
||||
return real;
|
||||
}
|
||||
function __wbg_adapter_20(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke(
|
||||
arg0,
|
||||
arg1,
|
||||
addHeapObject(arg2),
|
||||
);
|
||||
}
|
||||
|
||||
function __wbg_adapter_21(arg0, arg1) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__destroy(
|
||||
arg0,
|
||||
arg1,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
export function main() {
|
||||
wasm.main();
|
||||
}
|
||||
|
||||
let cachedUint32Memory0 = null;
|
||||
|
||||
function getUint32Memory0() {
|
||||
if (cachedUint32Memory0 === null || cachedUint32Memory0.byteLength === 0) {
|
||||
cachedUint32Memory0 = new Uint32Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedUint32Memory0;
|
||||
}
|
||||
|
||||
function passArrayJsValueToWasm0(array, malloc) {
|
||||
const ptr = malloc(array.length * 4, 4) >>> 0;
|
||||
const mem = getUint32Memory0();
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
mem[ptr / 4 + i] = addHeapObject(array[i]);
|
||||
}
|
||||
WASM_VECTOR_LEN = array.length;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
function handleError(f, args) {
|
||||
try {
|
||||
return f.apply(this, args);
|
||||
} catch (e) {
|
||||
wasm.__wbindgen_exn_store(addHeapObject(e));
|
||||
}
|
||||
}
|
||||
function __wbg_adapter_39(arg0, arg1, arg2, arg3) {
|
||||
wasm.wasm_bindgen__convert__closures__invoke2_mut(
|
||||
arg0,
|
||||
arg1,
|
||||
addHeapObject(arg2),
|
||||
addHeapObject(arg3),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* JavaScript wrapper for [`PPOM`]
|
||||
*/
|
||||
export class PPOM {
|
||||
static __wrap(ptr) {
|
||||
ptr = ptr >>> 0;
|
||||
const obj = Object.create(PPOM.prototype);
|
||||
obj.__wbg_ptr = ptr;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__destroy_into_raw() {
|
||||
const ptr = this.__wbg_ptr;
|
||||
this.__wbg_ptr = 0;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
free() {
|
||||
const ptr = this.__destroy_into_raw();
|
||||
wasm.__wbg_ppom_free(ptr);
|
||||
}
|
||||
/**
|
||||
* @param {Function} json_rpc_callback
|
||||
* @param {any[]} files
|
||||
* @returns {Promise<PPOM>}
|
||||
*/
|
||||
static new(json_rpc_callback, files) {
|
||||
const ptr0 = passArrayJsValueToWasm0(files, wasm.__wbindgen_malloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.ppom_new(addHeapObject(json_rpc_callback), ptr0, len0);
|
||||
return takeObject(ret);
|
||||
}
|
||||
/**
|
||||
* @param {any} request
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
validateJsonRpc(request) {
|
||||
const ret = wasm.ppom_validateJsonRpc(
|
||||
this.__wbg_ptr,
|
||||
addHeapObject(request),
|
||||
);
|
||||
return takeObject(ret);
|
||||
}
|
||||
}
|
||||
|
||||
async function __wbg_load(module, imports) {
|
||||
if (typeof Response === 'function' && module instanceof Response) {
|
||||
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
||||
try {
|
||||
return await WebAssembly.instantiateStreaming(module, imports);
|
||||
} catch (e) {
|
||||
if (module.headers.get('Content-Type') != 'application/wasm') {
|
||||
console.warn(
|
||||
'`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n',
|
||||
e,
|
||||
);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bytes = await module.arrayBuffer();
|
||||
return await WebAssembly.instantiate(bytes, imports);
|
||||
} else {
|
||||
const instance = await WebAssembly.instantiate(module, imports);
|
||||
|
||||
if (instance instanceof WebAssembly.Instance) {
|
||||
return { instance, module };
|
||||
} else {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function __wbg_get_imports() {
|
||||
const imports = {};
|
||||
imports.wbg = {};
|
||||
imports.wbg.__wbg_buffer_085ec1f694018c4f = function (arg0) {
|
||||
const ret = getObject(arg0).buffer;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_call_01734de55d61e11d = function () {
|
||||
return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));
|
||||
return addHeapObject(ret);
|
||||
}, arguments);
|
||||
};
|
||||
imports.wbg.__wbg_call_4c92f6aec1e1d6e6 = function () {
|
||||
return handleError(function (arg0, arg1, arg2, arg3) {
|
||||
const ret = getObject(arg0).call(
|
||||
getObject(arg1),
|
||||
getObject(arg2),
|
||||
getObject(arg3),
|
||||
);
|
||||
return addHeapObject(ret);
|
||||
}, arguments);
|
||||
};
|
||||
imports.wbg.__wbg_from_d7c216d4616bb368 = function (arg0) {
|
||||
const ret = Array.from(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_get_44be0491f933a435 = function (arg0, arg1) {
|
||||
const ret = getObject(arg0)[arg1 >>> 0];
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_length_72e2208bbc0efc61 = function (arg0) {
|
||||
const ret = getObject(arg0).length;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_length_d813e535247d427e = function (arg0) {
|
||||
const ret = getObject(arg0).length;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_length_fff51ee6522a1a18 = function (arg0) {
|
||||
const ret = getObject(arg0).length;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_new_43f1b47c28813cbd = function (arg0, arg1) {
|
||||
try {
|
||||
var state0 = { a: arg0, b: arg1 };
|
||||
var cb0 = (arg0, arg1) => {
|
||||
const a = state0.a;
|
||||
state0.a = 0;
|
||||
try {
|
||||
return __wbg_adapter_39(a, state0.b, arg0, arg1);
|
||||
} finally {
|
||||
state0.a = a;
|
||||
}
|
||||
};
|
||||
const ret = new Promise(cb0);
|
||||
return addHeapObject(ret);
|
||||
} finally {
|
||||
state0.a = state0.b = 0;
|
||||
}
|
||||
};
|
||||
imports.wbg.__wbg_new_8125e318e6245eed = function (arg0) {
|
||||
const ret = new Uint8Array(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_parse_670c19d4e984792e = function () {
|
||||
return handleError(function (arg0, arg1) {
|
||||
const ret = JSON.parse(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
}, arguments);
|
||||
};
|
||||
imports.wbg.__wbg_ppom_new = function (arg0) {
|
||||
const ret = PPOM.__wrap(arg0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_resolve_53698b95aaf7fcf8 = function (arg0) {
|
||||
const ret = Promise.resolve(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_set_5cf90238115182c3 = function (arg0, arg1, arg2) {
|
||||
getObject(arg0).set(getObject(arg1), arg2 >>> 0);
|
||||
};
|
||||
imports.wbg.__wbg_stringify_e25465938f3f611f = function () {
|
||||
return handleError(function (arg0) {
|
||||
const ret = JSON.stringify(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
}, arguments);
|
||||
};
|
||||
imports.wbg.__wbg_then_b2267541e2a73865 = function (arg0, arg1, arg2) {
|
||||
const ret = getObject(arg0).then(getObject(arg1), getObject(arg2));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_then_f7e06ee3c11698eb = function (arg0, arg1) {
|
||||
const ret = getObject(arg0).then(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_cb_drop = function (arg0) {
|
||||
const obj = takeObject(arg0).original;
|
||||
if (obj.cnt-- == 1) {
|
||||
obj.a = 0;
|
||||
return true;
|
||||
}
|
||||
const ret = false;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper_wasm_bindgen__closure__Closure_T___wrap__breaks_if_inlined =
|
||||
function (arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(
|
||||
arg0,
|
||||
arg1,
|
||||
__wbg_adapter_21,
|
||||
__wbg_adapter_20,
|
||||
);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_debug_string = function (arg0, arg1) {
|
||||
const ret = debugString(getObject(arg1));
|
||||
const ptr1 = passStringToWasm0(
|
||||
ret,
|
||||
wasm.__wbindgen_malloc,
|
||||
wasm.__wbindgen_realloc,
|
||||
);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
||||
};
|
||||
imports.wbg.__wbindgen_error_new = function (arg0, arg1) {
|
||||
const ret = new Error(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_is_undefined = function (arg0) {
|
||||
const ret = getObject(arg0) === undefined;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_memory = function () {
|
||||
const ret = wasm.memory;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_object_drop_ref = function (arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
imports.wbg.__wbindgen_string_get = function (arg0, arg1) {
|
||||
const obj = getObject(arg1);
|
||||
const ret = typeof obj === 'string' ? obj : undefined;
|
||||
var ptr1 = isLikeNone(ret)
|
||||
? 0
|
||||
: passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len1 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
||||
};
|
||||
imports.wbg.__wbindgen_string_new = function (arg0, arg1) {
|
||||
const ret = getStringFromWasm0(arg0, arg1);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_throw = function (arg0, arg1) {
|
||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
||||
function __wbg_init_memory(imports, maybe_memory) {}
|
||||
|
||||
function __wbg_finalize_init(instance, module) {
|
||||
wasm = instance.exports;
|
||||
__wbg_init.__wbindgen_wasm_module = module;
|
||||
cachedInt32Memory0 = null;
|
||||
cachedUint32Memory0 = null;
|
||||
cachedUint8Memory0 = null;
|
||||
|
||||
wasm.__wbindgen_start();
|
||||
return wasm;
|
||||
}
|
||||
|
||||
function initSync(module) {
|
||||
if (wasm !== undefined) return wasm;
|
||||
|
||||
const imports = __wbg_get_imports();
|
||||
|
||||
__wbg_init_memory(imports);
|
||||
|
||||
if (!(module instanceof WebAssembly.Module)) {
|
||||
module = new WebAssembly.Module(module);
|
||||
}
|
||||
|
||||
const instance = new WebAssembly.Instance(module, imports);
|
||||
|
||||
return __wbg_finalize_init(instance, module);
|
||||
}
|
||||
|
||||
async function __wbg_init(input) {
|
||||
if (wasm !== undefined) return wasm;
|
||||
|
||||
const imports = __wbg_get_imports();
|
||||
|
||||
if (
|
||||
typeof input === 'string' ||
|
||||
(typeof Request === 'function' && input instanceof Request) ||
|
||||
(typeof URL === 'function' && input instanceof URL)
|
||||
) {
|
||||
input = fetch(input);
|
||||
}
|
||||
|
||||
__wbg_init_memory(imports);
|
||||
|
||||
const { instance, module } = await __wbg_load(await input, imports);
|
||||
|
||||
return __wbg_finalize_init(instance, module);
|
||||
}
|
||||
|
||||
export { initSync };
|
||||
export default __wbg_init;
|
@ -116,7 +116,7 @@ async function switchEthereumChainHandler(
|
||||
if (
|
||||
Object.values(BUILT_IN_INFURA_NETWORKS)
|
||||
.map(({ chainId: id }) => id)
|
||||
.includes(chainId)
|
||||
.includes(_chainId)
|
||||
) {
|
||||
await setProviderType(approvedRequestData.type);
|
||||
} else {
|
||||
|
@ -0,0 +1,128 @@
|
||||
import {
|
||||
CHAIN_IDS,
|
||||
NETWORK_TYPES,
|
||||
} from '../../../../../shared/constants/network';
|
||||
import switchEthereumChain from './switch-ethereum-chain';
|
||||
|
||||
const NON_INFURA_CHAIN_ID = '0x123456789';
|
||||
|
||||
const mockRequestUserApproval = ({ requestData }) => {
|
||||
return Promise.resolve(requestData);
|
||||
};
|
||||
|
||||
const MOCK_MAINNET_CONFIGURATION = {
|
||||
id: 123,
|
||||
chainId: CHAIN_IDS.MAINNET,
|
||||
type: NETWORK_TYPES.MAINNET,
|
||||
};
|
||||
const MOCK_LINEA_MAINNET_CONFIGURATION = {
|
||||
id: 123,
|
||||
chainId: CHAIN_IDS.LINEA_MAINNET,
|
||||
type: NETWORK_TYPES.LINEA_MAINNET,
|
||||
};
|
||||
|
||||
describe('switchEthereumChainHandler', () => {
|
||||
it('should call setProviderType when switching to a built in infura network', async () => {
|
||||
const mockSetProviderType = jest.fn();
|
||||
const mockSetActiveNetwork = jest.fn();
|
||||
const switchEthereumChainHandler = switchEthereumChain.implementation;
|
||||
await switchEthereumChainHandler(
|
||||
{
|
||||
origin: 'example.com',
|
||||
params: [{ chainId: CHAIN_IDS.MAINNET }],
|
||||
},
|
||||
{},
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
{
|
||||
getCurrentChainId: () => NON_INFURA_CHAIN_ID,
|
||||
findNetworkConfigurationBy: () => MOCK_MAINNET_CONFIGURATION,
|
||||
setProviderType: mockSetProviderType,
|
||||
setActiveNetwork: mockSetActiveNetwork,
|
||||
requestUserApproval: mockRequestUserApproval,
|
||||
},
|
||||
);
|
||||
expect(mockSetProviderType).toHaveBeenCalledTimes(1);
|
||||
expect(mockSetProviderType).toHaveBeenCalledWith(
|
||||
MOCK_MAINNET_CONFIGURATION.type,
|
||||
);
|
||||
});
|
||||
|
||||
it('should call setProviderType when switching to a built in infura network, when chainId from request is lower case', async () => {
|
||||
const mockSetProviderType = jest.fn();
|
||||
const mockSetActiveNetwork = jest.fn();
|
||||
const switchEthereumChainHandler = switchEthereumChain.implementation;
|
||||
await switchEthereumChainHandler(
|
||||
{
|
||||
origin: 'example.com',
|
||||
params: [{ chainId: CHAIN_IDS.LINEA_MAINNET.toLowerCase() }],
|
||||
},
|
||||
{},
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
{
|
||||
getCurrentChainId: () => NON_INFURA_CHAIN_ID,
|
||||
findNetworkConfigurationBy: () => MOCK_LINEA_MAINNET_CONFIGURATION,
|
||||
setProviderType: mockSetProviderType,
|
||||
setActiveNetwork: mockSetActiveNetwork,
|
||||
requestUserApproval: mockRequestUserApproval,
|
||||
},
|
||||
);
|
||||
expect(mockSetProviderType).toHaveBeenCalledTimes(1);
|
||||
expect(mockSetProviderType).toHaveBeenCalledWith(
|
||||
MOCK_LINEA_MAINNET_CONFIGURATION.type,
|
||||
);
|
||||
});
|
||||
|
||||
it('should call setProviderType when switching to a built in infura network, when chainId from request is upper case', async () => {
|
||||
const mockSetProviderType = jest.fn();
|
||||
const mockSetActiveNetwork = jest.fn();
|
||||
const switchEthereumChainHandler = switchEthereumChain.implementation;
|
||||
await switchEthereumChainHandler(
|
||||
{
|
||||
origin: 'example.com',
|
||||
params: [{ chainId: CHAIN_IDS.LINEA_MAINNET.toUpperCase() }],
|
||||
},
|
||||
{},
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
{
|
||||
getCurrentChainId: () => NON_INFURA_CHAIN_ID,
|
||||
findNetworkConfigurationBy: () => MOCK_LINEA_MAINNET_CONFIGURATION,
|
||||
setProviderType: mockSetProviderType,
|
||||
setActiveNetwork: mockSetActiveNetwork,
|
||||
requestUserApproval: mockRequestUserApproval,
|
||||
},
|
||||
);
|
||||
expect(mockSetProviderType).toHaveBeenCalledTimes(1);
|
||||
expect(mockSetProviderType).toHaveBeenCalledWith(
|
||||
MOCK_LINEA_MAINNET_CONFIGURATION.type,
|
||||
);
|
||||
});
|
||||
|
||||
it('should call setActiveNetwork when switching to a custom network', async () => {
|
||||
const mockSetProviderType = jest.fn();
|
||||
const mockSetActiveNetwork = jest.fn();
|
||||
const switchEthereumChainHandler = switchEthereumChain.implementation;
|
||||
await switchEthereumChainHandler(
|
||||
{
|
||||
origin: 'example.com',
|
||||
params: [{ chainId: NON_INFURA_CHAIN_ID }],
|
||||
},
|
||||
{},
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
{
|
||||
getCurrentChainId: () => CHAIN_IDS.MAINNET,
|
||||
findNetworkConfigurationBy: () => MOCK_MAINNET_CONFIGURATION,
|
||||
setProviderType: mockSetProviderType,
|
||||
setActiveNetwork: mockSetActiveNetwork,
|
||||
requestUserApproval: mockRequestUserApproval,
|
||||
},
|
||||
);
|
||||
expect(mockSetActiveNetwork).toHaveBeenCalledTimes(1);
|
||||
expect(mockSetActiveNetwork).toHaveBeenCalledWith(
|
||||
MOCK_MAINNET_CONFIGURATION.id,
|
||||
);
|
||||
});
|
||||
});
|
@ -133,8 +133,45 @@ export default function setupSentry({ release, getState }) {
|
||||
Sentry.init({
|
||||
dsn: sentryTarget,
|
||||
debug: METAMASK_DEBUG,
|
||||
/**
|
||||
* autoSessionTracking defaults to true and operates by sending a session
|
||||
* packet to sentry. This session packet does not appear to be filtered out
|
||||
* via our beforeSend or FilterEvents integration. To avoid sending a
|
||||
* request before we have the state tree and can validate the users
|
||||
* preferences, we initiate this to false. Later, in startSession and
|
||||
* endSession we modify this option and start the session or end the
|
||||
* session manually.
|
||||
*
|
||||
* In sentry-install we call toggleSession after the page loads and state
|
||||
* is available, this handles initiating the session for a user who has
|
||||
* opted into MetaMetrics. This script is ran in both the background and UI
|
||||
* so it should be effective at starting the session in both places.
|
||||
*
|
||||
* In the MetaMetricsController the session is manually started or stopped
|
||||
* when the user opts in or out of MetaMetrics. This occurs in the
|
||||
* setParticipateInMetaMetrics function which is exposed to the UI via the
|
||||
* MetaMaskController.
|
||||
*
|
||||
* In actions.ts, after sending the updated participateInMetaMetrics flag
|
||||
* to the background, we call toggleSession to ensure sentry is kept in
|
||||
* sync with the user's preference.
|
||||
*
|
||||
* Types for the global Sentry object, and the new methods added as part of
|
||||
* this effort were added to global.d.ts in the types folder.
|
||||
*/
|
||||
autoSessionTracking: false,
|
||||
environment,
|
||||
integrations: [
|
||||
/**
|
||||
* Filtering of events must happen in this FilterEvents custom
|
||||
* integration instead of in the beforeSend handler because the Dedupe
|
||||
* integration is unaware of the beforeSend functionality. If an event is
|
||||
* queued in the sentry context, additional events of the same name will
|
||||
* be filtered out by Dedupe even if the original event was not sent due
|
||||
* to the beforeSend method returning null.
|
||||
*
|
||||
* @see https://github.com/MetaMask/metamask-extension/pull/15677
|
||||
*/
|
||||
new FilterEvents({ getMetaMetricsEnabled }),
|
||||
new Dedupe(),
|
||||
new ExtraErrorData(),
|
||||
@ -144,7 +181,64 @@ export default function setupSentry({ release, getState }) {
|
||||
beforeBreadcrumb: beforeBreadcrumb(getState),
|
||||
});
|
||||
|
||||
return Sentry;
|
||||
/**
|
||||
* As long as a reference to the Sentry Hub can be found, and the user has
|
||||
* opted into MetaMetrics, change the autoSessionTracking option and start
|
||||
* a new sentry session.
|
||||
*/
|
||||
const startSession = () => {
|
||||
const hub = Sentry.getCurrentHub?.();
|
||||
const options = hub.getClient?.().getOptions?.() ?? {};
|
||||
if (hub && getMetaMetricsEnabled() === true) {
|
||||
options.autoSessionTracking = true;
|
||||
hub.startSession();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* As long as a reference to the Sentry Hub can be found, and the user has
|
||||
* opted out of MetaMetrics, change the autoSessionTracking option and end
|
||||
* the current sentry session.
|
||||
*/
|
||||
const endSession = () => {
|
||||
const hub = Sentry.getCurrentHub?.();
|
||||
const options = hub.getClient?.().getOptions?.() ?? {};
|
||||
if (hub && getMetaMetricsEnabled() === false) {
|
||||
options.autoSessionTracking = false;
|
||||
hub.endSession();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Call the appropriate method (either startSession or endSession) depending
|
||||
* on the state of metaMetrics optin and the state of autoSessionTracking on
|
||||
* the Sentry client.
|
||||
*/
|
||||
const toggleSession = () => {
|
||||
const hub = Sentry.getCurrentHub?.();
|
||||
const options = hub.getClient?.().getOptions?.() ?? {
|
||||
autoSessionTracking: false,
|
||||
};
|
||||
const isMetaMetricsEnabled = getMetaMetricsEnabled();
|
||||
if (
|
||||
isMetaMetricsEnabled === true &&
|
||||
options.autoSessionTracking === false
|
||||
) {
|
||||
startSession();
|
||||
} else if (
|
||||
isMetaMetricsEnabled === false &&
|
||||
options.autoSessionTracking === true
|
||||
) {
|
||||
endSession();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...Sentry,
|
||||
startSession,
|
||||
endSession,
|
||||
toggleSession,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -352,6 +446,6 @@ function toMetamaskUrl(origUrl) {
|
||||
if (!filePath) {
|
||||
return origUrl;
|
||||
}
|
||||
const metamaskUrl = `metamask${filePath}`;
|
||||
const metamaskUrl = `/metamask${filePath}`;
|
||||
return metamaskUrl;
|
||||
}
|
||||
|
@ -6,10 +6,8 @@ import { JsonRpcEngine } from 'json-rpc-engine';
|
||||
import { createEngineStream } from 'json-rpc-middleware-stream';
|
||||
import { providerAsMiddleware } from '@metamask/eth-json-rpc-middleware';
|
||||
import { debounce } from 'lodash';
|
||||
import {
|
||||
KeyringController,
|
||||
keyringBuilderFactory,
|
||||
} from '@metamask/eth-keyring-controller';
|
||||
import { keyringBuilderFactory } from '@metamask/eth-keyring-controller';
|
||||
import { KeyringController } from '@metamask/keyring-controller';
|
||||
import createFilterMiddleware from 'eth-json-rpc-filters';
|
||||
import createSubscriptionManager from 'eth-json-rpc-filters/subscriptionManager';
|
||||
import { errorCodes as rpcErrorCodes, EthereumRpcError } from 'eth-rpc-errors';
|
||||
@ -76,6 +74,9 @@ import { CustodyController } from '@metamask-institutional/custody-controller';
|
||||
import { TransactionUpdateController } from '@metamask-institutional/transaction-update';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
import { SignatureController } from '@metamask/signature-controller';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
|
||||
import { PPOMController } from '@metamask/ppom-validator';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(desktop)
|
||||
// eslint-disable-next-line import/order
|
||||
@ -150,6 +151,10 @@ import { isManifestV3 } from '../../shared/modules/mv3.utils';
|
||||
import { hexToDecimal } from '../../shared/modules/conversion.utils';
|
||||
import { ACTION_QUEUE_METRICS_E2E_TEST } from '../../shared/constants/test-flags';
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
|
||||
import { createPPOMMiddleware } from './lib/ppom/ppom-middleware';
|
||||
import * as PPOMModule from './lib/ppom/ppom';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
import {
|
||||
onMessageReceived,
|
||||
checkForMultipleVersionsRunning,
|
||||
@ -210,6 +215,9 @@ import {
|
||||
} from './controllers/permissions';
|
||||
import createRPCMethodTrackingMiddleware from './lib/createRPCMethodTrackingMiddleware';
|
||||
import { securityProviderCheck } from './lib/security-provider-helpers';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
|
||||
import { IndexedDBPPOMStorage } from './lib/ppom/indexed-db-backend';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
import { updateCurrentLocale } from './translate';
|
||||
|
||||
export const METAMASK_CONTROLLER_EVENTS = {
|
||||
@ -489,16 +497,14 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.metaMetricsController.trackEvent({
|
||||
event: MetaMetricsEventName.NftAdded,
|
||||
category: MetaMetricsEventCategory.Wallet,
|
||||
properties: {
|
||||
sensitiveProperties: {
|
||||
token_contract_address: address,
|
||||
token_symbol: symbol,
|
||||
asset_type: AssetType.NFT,
|
||||
token_id: tokenId,
|
||||
token_standard: standard,
|
||||
asset_type: AssetType.NFT,
|
||||
source,
|
||||
},
|
||||
sensitiveProperties: {
|
||||
tokenId,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{},
|
||||
@ -630,6 +636,29 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.phishingController.setStalelistRefreshInterval(30 * SECOND);
|
||||
}
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
|
||||
this.ppomController = new PPOMController({
|
||||
messenger: this.controllerMessenger.getRestricted({
|
||||
name: 'PPOMController',
|
||||
}),
|
||||
storageBackend: new IndexedDBPPOMStorage('PPOMDB', 1),
|
||||
provider: this.provider,
|
||||
ppomProvider: { PPOM: PPOMModule.PPOM, ppomInit: PPOMModule.default },
|
||||
state: initState.PPOMController,
|
||||
chainId: this.networkController.state.providerConfig.chainId,
|
||||
onNetworkChange: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
'NetworkController:stateChange',
|
||||
),
|
||||
securityAlertsEnabled:
|
||||
this.preferencesController.store.getState().securityAlertsEnabled,
|
||||
onPreferencesChange: this.preferencesController.store.subscribe.bind(
|
||||
this.preferencesController.store,
|
||||
),
|
||||
cdnBaseUrl: process.env.BLOCKAID_FILE_CDN,
|
||||
});
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
const announcementMessenger = this.controllerMessenger.getRestricted({
|
||||
name: 'AnnouncementController',
|
||||
});
|
||||
@ -799,13 +828,43 @@ export default class MetamaskController extends EventEmitter {
|
||||
);
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
this.keyringController = new KeyringController({
|
||||
const keyringControllerMessenger = this.controllerMessenger.getRestricted({
|
||||
name: 'KeyringController',
|
||||
allowedEvents: [
|
||||
'KeyringController:accountRemoved',
|
||||
'KeyringController:lock',
|
||||
'KeyringController:stateChange',
|
||||
'KeyringController:unlock',
|
||||
],
|
||||
allowedActions: ['KeyringController:getState'],
|
||||
});
|
||||
|
||||
this.coreKeyringController = new KeyringController({
|
||||
keyringBuilders: additionalKeyrings,
|
||||
initState: initState.KeyringController,
|
||||
state: initState.KeyringController,
|
||||
encryptor: opts.encryptor || undefined,
|
||||
cacheEncryptionKey: isManifestV3,
|
||||
messenger: keyringControllerMessenger,
|
||||
removeIdentity: this.preferencesController.removeAddress.bind(
|
||||
this.preferencesController,
|
||||
),
|
||||
setAccountLabel: this.preferencesController.setAccountLabel.bind(
|
||||
this.preferencesController,
|
||||
),
|
||||
setSelectedAddress: this.preferencesController.setSelectedAddress.bind(
|
||||
this.preferencesController,
|
||||
),
|
||||
syncIdentities: this.preferencesController.syncAddresses.bind(
|
||||
this.preferencesController,
|
||||
),
|
||||
updateIdentities: this.preferencesController.setAddresses.bind(
|
||||
this.preferencesController,
|
||||
),
|
||||
});
|
||||
|
||||
this.keyringController =
|
||||
this.coreKeyringController.getEthKeyringController();
|
||||
|
||||
this.keyringController.memStore.subscribe((state) =>
|
||||
this._onKeyringControllerUpdate(state),
|
||||
);
|
||||
@ -892,10 +951,10 @@ export default class MetamaskController extends EventEmitter {
|
||||
}),
|
||||
setupSnapProvider: this.setupSnapProvider.bind(this),
|
||||
};
|
||||
this.snapExecutionService =
|
||||
this.opts.overrides?.createSnapExecutionService?.(
|
||||
snapExecutionServiceArgs,
|
||||
) || new IframeExecutionService(snapExecutionServiceArgs);
|
||||
|
||||
this.snapExecutionService = new IframeExecutionService(
|
||||
snapExecutionServiceArgs,
|
||||
);
|
||||
|
||||
const snapControllerMessenger = this.controllerMessenger.getRestricted({
|
||||
name: 'SnapController',
|
||||
@ -1163,28 +1222,6 @@ export default class MetamaskController extends EventEmitter {
|
||||
}),
|
||||
});
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
this.mmiController = new MMIController({
|
||||
mmiConfigurationController: this.mmiConfigurationController,
|
||||
keyringController: this.keyringController,
|
||||
txController: this.txController,
|
||||
securityProviderRequest: this.securityProviderRequest.bind(this),
|
||||
preferencesController: this.preferencesController,
|
||||
appStateController: this.appStateController,
|
||||
transactionUpdateController: this.transactionUpdateController,
|
||||
custodyController: this.custodyController,
|
||||
institutionalFeaturesController: this.institutionalFeaturesController,
|
||||
getState: this.getState.bind(this),
|
||||
getPendingNonce: this.getPendingNonce.bind(this),
|
||||
accountTracker: this.accountTracker,
|
||||
metaMetricsController: this.metaMetricsController,
|
||||
networkController: this.networkController,
|
||||
permissionController: this.permissionController,
|
||||
platform: this.platform,
|
||||
extension: this.extension,
|
||||
});
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
this.txController.on(`tx:status-update`, async (txId, status) => {
|
||||
if (
|
||||
status === TransactionStatus.confirmed ||
|
||||
@ -1346,6 +1383,29 @@ export default class MetamaskController extends EventEmitter {
|
||||
},
|
||||
);
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
this.mmiController = new MMIController({
|
||||
mmiConfigurationController: this.mmiConfigurationController,
|
||||
keyringController: this.keyringController,
|
||||
txController: this.txController,
|
||||
securityProviderRequest: this.securityProviderRequest.bind(this),
|
||||
preferencesController: this.preferencesController,
|
||||
appStateController: this.appStateController,
|
||||
transactionUpdateController: this.transactionUpdateController,
|
||||
custodyController: this.custodyController,
|
||||
institutionalFeaturesController: this.institutionalFeaturesController,
|
||||
getState: this.getState.bind(this),
|
||||
getPendingNonce: this.getPendingNonce.bind(this),
|
||||
accountTracker: this.accountTracker,
|
||||
metaMetricsController: this.metaMetricsController,
|
||||
networkController: this.networkController,
|
||||
permissionController: this.permissionController,
|
||||
signatureController: this.signatureController,
|
||||
platform: this.platform,
|
||||
extension: this.extension,
|
||||
});
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
this.swapsController = new SwapsController(
|
||||
{
|
||||
getBufferedGasLimit:
|
||||
@ -1463,6 +1523,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
// tx signing
|
||||
processTransaction: this.newUnapprovedTransaction.bind(this),
|
||||
// msg signing
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask)
|
||||
processEthSignMessage: this.signatureController.newUnsignedMessage.bind(
|
||||
this.signatureController,
|
||||
),
|
||||
@ -1482,8 +1543,25 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.signatureController.newUnsignedPersonalMessage.bind(
|
||||
this.signatureController,
|
||||
),
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
/* eslint-disable no-dupe-keys */
|
||||
processEthSignMessage: this.mmiController.newUnsignedMessage.bind(
|
||||
this.mmiController,
|
||||
),
|
||||
processTypedMessage: this.mmiController.newUnsignedMessage.bind(
|
||||
this.mmiController,
|
||||
),
|
||||
processTypedMessageV3: this.mmiController.newUnsignedMessage.bind(
|
||||
this.mmiController,
|
||||
),
|
||||
processTypedMessageV4: this.mmiController.newUnsignedMessage.bind(
|
||||
this.mmiController,
|
||||
),
|
||||
processPersonalMessage: this.mmiController.newUnsignedMessage.bind(
|
||||
this.mmiController,
|
||||
),
|
||||
setTypedMessageInProgress:
|
||||
this.signatureController.setTypedMessageInProgress.bind(
|
||||
this.signatureController,
|
||||
@ -1492,6 +1570,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.signatureController.setPersonalMessageInProgress.bind(
|
||||
this.signatureController,
|
||||
),
|
||||
/* eslint-enable no-dupe-keys */
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
processEncryptionPublicKey:
|
||||
@ -1529,6 +1608,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
SwapsController: this.swapsController.store,
|
||||
EnsController: this.ensController.store,
|
||||
ApprovalController: this.approvalController,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
|
||||
PPOMController: this.ppomController,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
};
|
||||
|
||||
this.store.updateStructure({
|
||||
@ -1571,6 +1653,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.institutionalFeaturesController.store,
|
||||
MmiConfigurationController: this.mmiConfigurationController.store,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
|
||||
PPOMController: this.ppomController,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
...resetOnRestartStore,
|
||||
});
|
||||
|
||||
@ -2053,9 +2138,21 @@ export default class MetamaskController extends EventEmitter {
|
||||
const { vault } = this.keyringController.store.getState();
|
||||
const isInitialized = Boolean(vault);
|
||||
|
||||
const flatState = this.memStore.getFlatState();
|
||||
|
||||
return {
|
||||
isInitialized,
|
||||
...this.memStore.getFlatState(),
|
||||
...flatState,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
// Snap state and source code is stripped out to prevent piping to the MetaMask UI.
|
||||
snapStates: {},
|
||||
snaps: Object.values(flatState.snaps ?? {}).reduce((acc, snap) => {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { sourceCode, ...rest } = snap;
|
||||
acc[snap.id] = rest;
|
||||
return acc;
|
||||
}, {}),
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
};
|
||||
}
|
||||
|
||||
@ -2125,6 +2222,12 @@ export default class MetamaskController extends EventEmitter {
|
||||
setOpenSeaEnabled: preferencesController.setOpenSeaEnabled.bind(
|
||||
preferencesController,
|
||||
),
|
||||
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
|
||||
setSecurityAlertsEnabled:
|
||||
preferencesController.setSecurityAlertsEnabled.bind(
|
||||
preferencesController,
|
||||
),
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
setIpfsGateway: preferencesController.setIpfsGateway.bind(
|
||||
preferencesController,
|
||||
),
|
||||
@ -2333,8 +2436,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
createSpeedUpTransaction: this.createSpeedUpTransaction.bind(this),
|
||||
estimateGas: this.estimateGas.bind(this),
|
||||
getNextNonce: this.getNextNonce.bind(this),
|
||||
addUnapprovedTransaction:
|
||||
txController.addUnapprovedTransaction.bind(txController),
|
||||
addTransaction: this.addTransaction.bind(this),
|
||||
addTransactionAndWaitForPublish:
|
||||
this.addTransactionAndWaitForPublish.bind(this),
|
||||
createTransactionEventFragment:
|
||||
txController.createTransactionEventFragment.bind(txController),
|
||||
getTransactions: txController.getTransactions.bind(txController),
|
||||
@ -2443,34 +2547,14 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.mmiConfigurationController.getConfiguration.bind(
|
||||
this.mmiConfigurationController,
|
||||
),
|
||||
setComplianceAuthData:
|
||||
this.institutionalFeaturesController.setComplianceAuthData.bind(
|
||||
this.institutionalFeaturesController,
|
||||
),
|
||||
deleteComplianceAuthData:
|
||||
this.institutionalFeaturesController.deleteComplianceAuthData.bind(
|
||||
this.institutionalFeaturesController,
|
||||
),
|
||||
generateComplianceReport:
|
||||
this.institutionalFeaturesController.generateComplianceReport.bind(
|
||||
this.institutionalFeaturesController,
|
||||
),
|
||||
syncReportsInProgress:
|
||||
this.institutionalFeaturesController.syncReportsInProgress.bind(
|
||||
this.institutionalFeaturesController,
|
||||
),
|
||||
removeConnectInstitutionalFeature:
|
||||
this.institutionalFeaturesController.removeConnectInstitutionalFeature.bind(
|
||||
this.institutionalFeaturesController,
|
||||
),
|
||||
getComplianceHistoricalReportsByAddress:
|
||||
this.institutionalFeaturesController.getComplianceHistoricalReportsByAddress.bind(
|
||||
this.institutionalFeaturesController,
|
||||
),
|
||||
removeAddTokenConnectRequest:
|
||||
this.institutionalFeaturesController.removeAddTokenConnectRequest.bind(
|
||||
this.institutionalFeaturesController,
|
||||
),
|
||||
showInteractiveReplacementTokenBanner:
|
||||
appStateController.showInteractiveReplacementTokenBanner.bind(
|
||||
appStateController,
|
||||
),
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
@ -3482,7 +3566,41 @@ export default class MetamaskController extends EventEmitter {
|
||||
* @param {object} [req] - The original request, containing the origin.
|
||||
*/
|
||||
async newUnapprovedTransaction(txParams, req) {
|
||||
return await this.txController.newUnapprovedTransaction(txParams, req);
|
||||
// Options are passed explicitly as an additional security measure
|
||||
// to ensure approval is not disabled
|
||||
const { result } = await this.txController.addTransaction(txParams, {
|
||||
actionId: req.id,
|
||||
method: req.method,
|
||||
origin: req.origin,
|
||||
// This is the default behaviour but specified here for clarity
|
||||
requireApproval: true,
|
||||
});
|
||||
|
||||
return await result;
|
||||
}
|
||||
|
||||
async addTransactionAndWaitForPublish(txParams, options) {
|
||||
const { transactionMeta, result } = await this.txController.addTransaction(
|
||||
txParams,
|
||||
options,
|
||||
);
|
||||
|
||||
await result;
|
||||
|
||||
return transactionMeta;
|
||||
}
|
||||
|
||||
async addTransaction(txParams, options) {
|
||||
const { transactionMeta, result } = await this.txController.addTransaction(
|
||||
txParams,
|
||||
options,
|
||||
);
|
||||
|
||||
result.catch(() => {
|
||||
// Not concerned with result
|
||||
});
|
||||
|
||||
return transactionMeta;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3910,6 +4028,10 @@ export default class MetamaskController extends EventEmitter {
|
||||
engine.push(createLoggerMiddleware({ origin }));
|
||||
engine.push(this.permissionLogController.createMiddleware());
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
|
||||
engine.push(createPPOMMiddleware(this.ppomController));
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
engine.push(
|
||||
createRPCMethodTrackingMiddleware({
|
||||
trackEvent: this.metaMetricsController.trackEvent.bind(
|
||||
@ -3963,6 +4085,12 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.approvalController.setFlowLoadingText.bind(
|
||||
this.approvalController,
|
||||
),
|
||||
showApprovalSuccess: this.approvalController.success.bind(
|
||||
this.approvalController,
|
||||
),
|
||||
showApprovalError: this.approvalController.error.bind(
|
||||
this.approvalController,
|
||||
),
|
||||
sendMetrics: this.metaMetricsController.trackEvent.bind(
|
||||
this.metaMetricsController,
|
||||
),
|
||||
|
224
app/scripts/migrations/089.test.ts
Normal file
224
app/scripts/migrations/089.test.ts
Normal file
@ -0,0 +1,224 @@
|
||||
import { migrate, version } from './089';
|
||||
|
||||
jest.mock('uuid', () => {
|
||||
const actual = jest.requireActual('uuid');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
v4: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('migration #89', () => {
|
||||
it('should update the version metadata', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 88,
|
||||
},
|
||||
data: {},
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.meta).toStrictEqual({
|
||||
version,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return state unaltered if there is no network controller state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 88,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('should return state unaltered if there is no network controller providerConfig state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
NetworkController: {
|
||||
networkConfigurations: {
|
||||
id1: {
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 88,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('should return state unaltered if the providerConfig already has an id', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
NetworkController: {
|
||||
networkConfigurations: {
|
||||
id1: {
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
providerConfig: {
|
||||
id: 'test',
|
||||
},
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 88,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('should return state unaltered if there is no network config with the same rpcUrl and the providerConfig', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
NetworkController: {
|
||||
networkConfigurations: {
|
||||
id1: {
|
||||
foo: 'bar',
|
||||
rpcUrl: 'http://foo.bar',
|
||||
},
|
||||
},
|
||||
providerConfig: {
|
||||
rpcUrl: 'http://baz.buzz',
|
||||
},
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 88,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('should update the provider config to have the id of a network config with the same rpcUrl', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
NetworkController: {
|
||||
networkConfigurations: {
|
||||
id1: {
|
||||
foo: 'bar',
|
||||
rpcUrl: 'http://foo.bar',
|
||||
id: 'test',
|
||||
},
|
||||
},
|
||||
providerConfig: {
|
||||
rpcUrl: 'http://foo.bar',
|
||||
},
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 88,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.data).toStrictEqual({
|
||||
other: 'data',
|
||||
NetworkController: {
|
||||
networkConfigurations: {
|
||||
id1: {
|
||||
foo: 'bar',
|
||||
rpcUrl: 'http://foo.bar',
|
||||
id: 'test',
|
||||
},
|
||||
},
|
||||
providerConfig: {
|
||||
rpcUrl: 'http://foo.bar',
|
||||
id: 'test',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should update the provider config to have the id of a network config with the same rpcUrl, even if there are other networks with the same chainId', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
NetworkController: {
|
||||
networkConfigurations: {
|
||||
id1: {
|
||||
foo: 'bar',
|
||||
rpcUrl: 'http://fizz.buzz',
|
||||
id: 'FAILEDtest',
|
||||
chainId: 1,
|
||||
},
|
||||
id2: {
|
||||
foo: 'bar',
|
||||
rpcUrl: 'http://foo.bar',
|
||||
id: 'PASSEDtest',
|
||||
},
|
||||
id3: {
|
||||
foo: 'bar',
|
||||
rpcUrl: 'http://baz.buzz',
|
||||
id: 'FAILEDtest',
|
||||
chainId: 1,
|
||||
},
|
||||
},
|
||||
providerConfig: {
|
||||
rpcUrl: 'http://foo.bar',
|
||||
chainId: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 88,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.data).toStrictEqual({
|
||||
other: 'data',
|
||||
NetworkController: {
|
||||
networkConfigurations: {
|
||||
id1: {
|
||||
foo: 'bar',
|
||||
rpcUrl: 'http://fizz.buzz',
|
||||
id: 'FAILEDtest',
|
||||
chainId: 1,
|
||||
},
|
||||
id2: {
|
||||
foo: 'bar',
|
||||
rpcUrl: 'http://foo.bar',
|
||||
id: 'PASSEDtest',
|
||||
},
|
||||
id3: {
|
||||
foo: 'bar',
|
||||
rpcUrl: 'http://baz.buzz',
|
||||
id: 'FAILEDtest',
|
||||
chainId: 1,
|
||||
},
|
||||
},
|
||||
providerConfig: {
|
||||
rpcUrl: 'http://foo.bar',
|
||||
id: 'PASSEDtest',
|
||||
chainId: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
71
app/scripts/migrations/089.ts
Normal file
71
app/scripts/migrations/089.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { hasProperty, isObject } from '@metamask/utils';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
export const version = 89;
|
||||
|
||||
/**
|
||||
* Add an `id` to the `providerConfig` object.
|
||||
*
|
||||
* @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist.
|
||||
* @param originalVersionedData.meta - State metadata.
|
||||
* @param originalVersionedData.meta.version - The current state version.
|
||||
* @param originalVersionedData.data - The persisted MetaMask state, keyed by controller.
|
||||
* @returns Updated versioned MetaMask extension state.
|
||||
*/
|
||||
export async function migrate(originalVersionedData: {
|
||||
meta: { version: number };
|
||||
data: Record<string, unknown>;
|
||||
}) {
|
||||
const versionedData = cloneDeep(originalVersionedData);
|
||||
versionedData.meta.version = version;
|
||||
versionedData.data = transformState(versionedData.data);
|
||||
return versionedData;
|
||||
}
|
||||
|
||||
function transformState(state: Record<string, unknown>) {
|
||||
if (
|
||||
hasProperty(state, 'NetworkController') &&
|
||||
isObject(state.NetworkController) &&
|
||||
hasProperty(state.NetworkController, 'providerConfig') &&
|
||||
isObject(state.NetworkController.providerConfig)
|
||||
) {
|
||||
const { networkConfigurations, providerConfig } = state.NetworkController;
|
||||
|
||||
if (!isObject(networkConfigurations)) {
|
||||
return state;
|
||||
}
|
||||
|
||||
if (providerConfig.id) {
|
||||
return state;
|
||||
}
|
||||
|
||||
let newProviderConfigId;
|
||||
|
||||
for (const networkConfigurationId of Object.keys(networkConfigurations)) {
|
||||
const networkConfiguration =
|
||||
networkConfigurations[networkConfigurationId];
|
||||
if (!isObject(networkConfiguration)) {
|
||||
return state;
|
||||
}
|
||||
if (networkConfiguration.rpcUrl === providerConfig.rpcUrl) {
|
||||
newProviderConfigId = networkConfiguration.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!newProviderConfigId) {
|
||||
return state;
|
||||
}
|
||||
|
||||
state.NetworkController.providerConfig = {
|
||||
...providerConfig,
|
||||
id: newProviderConfigId,
|
||||
};
|
||||
|
||||
return {
|
||||
...state,
|
||||
NetworkController: state.NetworkController,
|
||||
};
|
||||
}
|
||||
return state;
|
||||
}
|
@ -92,6 +92,7 @@ import * as m085 from './085';
|
||||
import * as m086 from './086';
|
||||
import * as m087 from './087';
|
||||
import * as m088 from './088';
|
||||
import * as m089 from './089';
|
||||
|
||||
const migrations = [
|
||||
m002,
|
||||
@ -181,6 +182,7 @@ const migrations = [
|
||||
m086,
|
||||
m087,
|
||||
m088,
|
||||
m089,
|
||||
];
|
||||
|
||||
export default migrations;
|
||||
|
@ -8,3 +8,22 @@ global.sentry = setupSentry({
|
||||
release: process.env.METAMASK_VERSION,
|
||||
getState: () => global.stateHooks?.getSentryState?.() || {},
|
||||
});
|
||||
|
||||
/**
|
||||
* As soon as state is available via getSentryState we can call the
|
||||
* toggleSession method added to sentry in setupSentry to start automatic
|
||||
* session tracking.
|
||||
*/
|
||||
function waitForStateHooks() {
|
||||
if (global.stateHooks.getSentryState) {
|
||||
// sentry is not defined in dev mode, so if sentry doesn't exist at this
|
||||
// point it means that we are in dev mode and do not need to toggle the
|
||||
// session. Using optional chaining is sufficient to prevent the error in
|
||||
// development.
|
||||
global.sentry?.toggleSession();
|
||||
} else {
|
||||
setTimeout(waitForStateHooks, 100);
|
||||
}
|
||||
}
|
||||
|
||||
waitForStateHooks();
|
||||
|
@ -1,6 +1,3 @@
|
||||
// polyfills
|
||||
import '@formatjs/intl-relativetimeformat/polyfill';
|
||||
|
||||
// dev only, "react-devtools" import is skipped in prod builds
|
||||
import 'react-devtools';
|
||||
|
||||
|
12
builds.yml
12
builds.yml
@ -47,12 +47,13 @@ buildTypes:
|
||||
- desktop
|
||||
- build-flask
|
||||
- keyring-snaps
|
||||
# - blockaid
|
||||
env:
|
||||
- INFURA_FLASK_PROJECT_ID
|
||||
- SEGMENT_FLASK_WRITE_KEY
|
||||
- ALLOW_LOCAL_SNAPS: true
|
||||
- REQUIRE_SNAPS_ALLOWLIST: false
|
||||
- IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/0.36.1-flask.1/index.html
|
||||
- IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/0.37.1-flask.1/index.html
|
||||
- SUPPORT_LINK: https://metamask-flask.zendesk.com/hc
|
||||
- SUPPORT_REQUEST_LINK: https://metamask-flask.zendesk.com/hc/en-us/requests/new
|
||||
- INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID
|
||||
@ -71,7 +72,7 @@ buildTypes:
|
||||
- SEGMENT_FLASK_WRITE_KEY
|
||||
- ALLOW_LOCAL_SNAPS: true
|
||||
- REQUIRE_SNAPS_ALLOWLIST: false
|
||||
- IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/0.36.1-flask.1/index.html
|
||||
- IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/0.37.1-flask.1/index.html
|
||||
- SUPPORT_LINK: https://metamask-flask.zendesk.com/hc
|
||||
- SUPPORT_REQUEST_LINK: https://metamask-flask.zendesk.com/hc/en-us/requests/new
|
||||
- INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID
|
||||
@ -87,9 +88,9 @@ buildTypes:
|
||||
- SEGMENT_MMI_WRITE_KEY
|
||||
- INFURA_ENV_KEY_REF: INFURA_MMI_PROJECT_ID
|
||||
- SEGMENT_WRITE_KEY_REF: SEGMENT_MMI_WRITE_KEY
|
||||
- MMI_CONFIGURATION_SERVICE_URL: https://configuration.metamask-institutional.io/v1/configuration/default
|
||||
- SUPPORT_LINK: https://mmi-support.zendesk.com/hc/en-us
|
||||
- SUPPORT_REQUEST_LINK: https://mmi-support.zendesk.com/hc/en-us/requests/new
|
||||
- MMI_CONFIGURATION_SERVICE_URL
|
||||
# For some reason, MMI uses this type of versioning
|
||||
# Leaving it on for backwards compatibility
|
||||
isPrerelease: true
|
||||
@ -116,6 +117,9 @@ features:
|
||||
- DISABLE_WEB_SOCKET_ENCRYPTION: false
|
||||
- SKIP_OTP_PAIRING_FLOW: false
|
||||
- WEB_SOCKET_PORT: null
|
||||
blockaid:
|
||||
env:
|
||||
- BLOCKAID_FILE_CDN: null
|
||||
|
||||
###
|
||||
# Build Type code extensions. Things like different support links, warning pages, banners
|
||||
@ -224,6 +228,8 @@ env:
|
||||
- NODE_DEBUG: ''
|
||||
# Used by react-devtools-core
|
||||
- EDITOR_URL: ''
|
||||
# CDN for blockaid files
|
||||
- BLOCKAID_FILE_CDN
|
||||
|
||||
###
|
||||
# Meta variables
|
||||
|
@ -11,7 +11,12 @@ const VARIABLES_REQUIRED_IN_PRODUCTION = {
|
||||
main: ['INFURA_PROD_PROJECT_ID', 'SEGMENT_PROD_WRITE_KEY', 'SENTRY_DSN'],
|
||||
beta: ['INFURA_BETA_PROJECT_ID', 'SEGMENT_BETA_WRITE_KEY', 'SENTRY_DSN'],
|
||||
flask: ['INFURA_FLASK_PROJECT_ID', 'SEGMENT_FLASK_WRITE_KEY', 'SENTRY_DSN'],
|
||||
mmi: ['INFURA_MMI_PROJECT_ID', 'SEGMENT_MMI_WRITE_KEY', 'SENTRY_DSN'],
|
||||
mmi: [
|
||||
'INFURA_MMI_PROJECT_ID',
|
||||
'MMI_CONFIGURATION_SERVICE_URL',
|
||||
'SEGMENT_MMI_WRITE_KEY',
|
||||
'SENTRY_DSN',
|
||||
],
|
||||
};
|
||||
|
||||
async function fromIniFile(filepath) {
|
||||
|
@ -99,6 +99,8 @@ async function defineAndRunBuildTasks() {
|
||||
'navigator',
|
||||
'harden',
|
||||
'console',
|
||||
'WeakSet',
|
||||
'Event',
|
||||
'Image', // Used by browser to generate notifications
|
||||
// globals chromedriver needs to function
|
||||
/cdc_[a-zA-Z0-9]+_[a-zA-Z]+/iu,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user