Fix develop conflicts

feature/default_network_editable
seaona 2 years ago
commit 4d58f4cc4e
  1. 1
      .browserslistrc
  2. 27
      .circleci/config.yml
  3. 2
      .circleci/scripts/firefox-install.sh
  4. 32
      .github/PULL_REQUEST_TEMPLATE.md
  5. 2
      .github/workflows/cla.yml
  6. 2
      .github/workflows/codeql-analysis.yml
  7. 2
      .github/workflows/crowdin_action.yml
  8. 4
      .storybook/initial-states/transactions.js
  9. 81
      .storybook/test-data.js
  10. 13
      README.md
  11. 2
      app/_locales/de/messages.json
  12. 82
      app/_locales/en/messages.json
  13. 2
      app/_locales/es/messages.json
  14. 566
      app/_locales/it/messages.json
  15. 994
      app/_locales/zh_TW/messages.json
  16. 2
      app/build-types/beta/manifest/chrome.json
  17. 0
      app/images/icons/icon-minus-outline.svg
  18. 3
      app/images/icons/icon-user-cirlce-add-filled.svg
  19. 4
      app/scripts/app-init.js
  20. 72
      app/scripts/background.js
  21. 209
      app/scripts/contentscript.js
  22. 23
      app/scripts/controllers/app-state.js
  23. 5
      app/scripts/controllers/ens/index.js
  24. 165
      app/scripts/controllers/metametrics.js
  25. 129
      app/scripts/controllers/metametrics.test.js
  26. 2
      app/scripts/controllers/permissions/flask/snap-permissions.js
  27. 4
      app/scripts/controllers/permissions/specifications.js
  28. 8
      app/scripts/controllers/permissions/specifications.test.js
  29. 34
      app/scripts/controllers/preferences.js
  30. 12
      app/scripts/controllers/preferences.test.js
  31. 4
      app/scripts/controllers/swaps.js
  32. 5
      app/scripts/controllers/transactions/index.js
  33. 151
      app/scripts/lib/account-tracker.js
  34. 8
      app/scripts/lib/decrypt-message-manager.js
  35. 8
      app/scripts/lib/encryption-public-key-manager.js
  36. 8
      app/scripts/lib/message-manager.js
  37. 8
      app/scripts/lib/personal-message-manager.js
  38. 6
      app/scripts/lib/seed-phrase-verifier.js
  39. 3
      app/scripts/lib/seed-phrase-verifier.test.js
  40. 13
      app/scripts/lib/segment/analytics.js
  41. 10
      app/scripts/lib/stream-utils.js
  42. 8
      app/scripts/lib/typed-message-manager.js
  43. 17
      app/scripts/lib/util.js
  44. 6
      app/scripts/metamask-controller.actions.test.js
  45. 320
      app/scripts/metamask-controller.js
  46. 23
      app/scripts/metamask-controller.test.js
  47. 46
      app/scripts/migrations/076.js
  48. 143
      app/scripts/migrations/076.test.js
  49. 59
      app/scripts/migrations/077.js
  50. 322
      app/scripts/migrations/077.test.js
  51. 4
      app/scripts/migrations/index.js
  52. 139
      app/scripts/platforms/extension.js
  53. 4
      app/scripts/sentry-install.js
  54. 109
      app/scripts/ui.js
  55. 2
      development/build/index.js
  56. 6
      development/build/styles.js
  57. 10
      development/generate-icon-names.js
  58. 68
      development/metamaskbot-build-announce.js
  59. 2
      development/ts-migration-dashboard/files-to-convert.json
  60. 1
      docs/QA_Guide.md
  61. 4
      docs/generating-fixture-data.md
  62. 6
      jest.config.js
  63. 20
      jest.stories.config.js
  64. 137
      lavamoat/browserify/beta/policy.json
  65. 426
      lavamoat/browserify/flask/policy.json
  66. 137
      lavamoat/browserify/main/policy.json
  67. 2
      lavamoat/browserify/policy-override.json
  68. 126
      lavamoat/build-system/policy.json
  69. 18
      package.json
  70. 18
      patches/luxon+3.1.0.patch
  71. 8
      shared/constants/app.ts
  72. 3
      shared/constants/hardware-wallets.js
  73. 7
      shared/constants/keyrings.js
  74. 2
      shared/constants/network.ts
  75. 2
      shared/constants/permissions.test.js
  76. 3
      shared/constants/permissions.ts
  77. 37
      shared/constants/swaps.js
  78. 4
      shared/constants/tokens.js
  79. 4
      shared/constants/transaction.js
  80. 48
      shared/lib/error-utils.js
  81. 6
      shared/lib/error-utils.test.js
  82. 4
      shared/lib/swaps-utils.js
  83. 55
      shared/modules/browser-runtime.utils.js
  84. 54
      shared/modules/browser-runtime.utils.test.js
  85. 2
      shared/modules/transaction.utils.js
  86. 15
      shared/notifications/index.js
  87. 41
      test/data/mock-state.json
  88. 21
      test/e2e/fixture-builder.js
  89. 26
      test/e2e/helpers.js
  90. 16
      test/e2e/metamask-ui.spec.js
  91. 2
      test/e2e/restore/MetaMaskUserData.json
  92. 17
      test/e2e/run-all.js
  93. 2
      test/e2e/snaps/enums.js
  94. 141
      test/e2e/snaps/test-snap-bip-32.spec.js
  95. 77
      test/e2e/snaps/test-snap-bip-44.spec.js
  96. 51
      test/e2e/snaps/test-snap-confirm.spec.js
  97. 54
      test/e2e/snaps/test-snap-error.spec.js
  98. 78
      test/e2e/snaps/test-snap-installed.spec.js
  99. 61
      test/e2e/snaps/test-snap-managestate.spec.js
  100. 49
      test/e2e/snaps/test-snap-notification.spec.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1 @@
chrome >= 66, firefox >= 68

@ -59,12 +59,9 @@ workflows:
- prep-build-test-flask:
requires:
- prep-deps
- test-storybook:
requires:
- prep-deps
- prep-build-storybook:
requires:
- test-storybook
- prep-deps
- prep-build-ts-migration-dashboard:
requires:
- prep-deps
@ -432,16 +429,6 @@ jobs:
paths:
- development/ts-migration-dashboard/build
test-storybook:
executor: node-browsers
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Test Storybook
command: yarn storybook:test
test-yarn-dedupe:
executor: node-browsers
steps:
@ -532,6 +519,7 @@ jobs:
test-e2e-chrome:
executor: node-browsers
parallelism: 8
steps:
- checkout
- run:
@ -556,9 +544,10 @@ jobs:
- store_artifacts:
path: test-artifacts
destination: test-artifacts
test-e2e-chrome-mv3:
executor: node-browsers
parallelism: 8
steps:
- checkout
- run:
@ -586,6 +575,7 @@ jobs:
test-e2e-firefox-snaps:
executor: node-browsers
parallelism: 2
steps:
- checkout
- run:
@ -613,6 +603,7 @@ jobs:
test-e2e-chrome-snaps:
executor: node-browsers
parallelism: 2
steps:
- checkout
- run:
@ -640,6 +631,7 @@ jobs:
test-e2e-firefox:
executor: node-browsers-medium-plus
parallelism: 8
steps:
- checkout
- run:
@ -808,6 +800,11 @@ jobs:
- store_artifacts:
path: development/ts-migration-dashboard/build
destination: ts-migration-dashboard
- run:
name: Set branch parent commit env var
command: |
echo "export PARENT_COMMIT=$(git rev-parse "$(git rev-list --topo-order --reverse HEAD ^origin/develop | head -1)"^)" >> $BASH_ENV
source $BASH_ENV
- run:
name: build:announce
command: ./development/metamaskbot-build-announce.js

@ -4,7 +4,7 @@ set -e
set -u
set -o pipefail
FIREFOX_VERSION='102.0'
FIREFOX_VERSION='106.0.4'
FIREFOX_BINARY="firefox-${FIREFOX_VERSION}.tar.bz2"
FIREFOX_BINARY_URL="https://ftp.mozilla.org/pub/firefox/releases/${FIREFOX_VERSION}/linux-x86_64/en-US/${FIREFOX_BINARY}"
FIREFOX_PATH='/opt/firefox'

@ -6,18 +6,6 @@ Thanks for the pull request. Take a moment to answer these questions so that rev
* What is the current state of things and why does it need to change?
* What is the solution your changes offer and how does it work?
Below is a template to give you some ideas. Feel free to use your own words!
Currently, ...
This is a problem because ...
In order to solve this problem, this pull request ...
-->
## More Information
<!--
Are there any issues, Slack conversations, Zendesk issues, user stories, etc. reviewers should consult to understand this pull request better? For instance:
* Fixes #12345
@ -46,14 +34,20 @@ How should reviewers and QA manually test your changes? For instance:
- Then do this
-->
## Pre-Merge Checklist
## Pre-merge author checklist
- [ ] I've clearly explained:
- [ ] What problem this PR is solving
- [ ] How this problem was solved
- [ ] How reviewers can test my changes
- [ ] Sufficient automated test coverage has been added
## Pre-merge reviewer checklist
- [ ] PR template is filled out
- [ ] **IF** this PR fixes a bug, a test that _would have_ caught the bug has been added
- [ ] Manual testing (e.g. pull and build branch, run in browser, test code being changed)
- [ ] PR is linked to the appropriate GitHub issue
- [ ] PR has been added to the appropriate release Milestone
- [ ] **IF** this PR fixes a bug in the release milestone, add this PR to the release milestone
### + If there are functional changes:
If further QA is required (e.g. new feature, complex testing steps, large refactor), add the `Extension QA Board` label.
- [ ] Manual testing complete & passed
- [ ] "Extension QA Board" label has been applied
In this case, a QA Engineer approval will be be required.

@ -8,7 +8,7 @@ on:
jobs:
CLABot:
if: github.event_name == 'pull_request_target' || contains(github.event.comment.html_url, '/pull/')
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: write

@ -23,7 +23,7 @@ on:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
permissions:
actions: read
contents: read

@ -13,7 +13,7 @@ on:
jobs:
synchronize-with-crowdin:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:

@ -1454,7 +1454,7 @@ export const MOCK_TRANSACTION_BY_TYPE = {
dappSuggestedGasFees: null,
sendFlowHistory: [
{
entry: 'sendFlow - user set asset type to COLLECTIBLE',
entry: 'sendFlow - user set asset type to NFT',
timestamp: 1653457317999,
},
{
@ -1504,7 +1504,7 @@ export const MOCK_TRANSACTION_BY_TYPE = {
dappSuggestedGasFees: null,
sendFlowHistory: [
{
entry: 'sendFlow - user set asset type to COLLECTIBLE',
entry: 'sendFlow - user set asset type to NFT',
timestamp: 1653457317999,
},
{

@ -1,4 +1,6 @@
import { draftTransactionInitialState } from '../ui/ducks/send';
import { KEYRING_TYPES } from '../shared/constants/keyrings';
const state = {
invalidCustomNetwork: {
state: 'CLOSED',
@ -29,7 +31,7 @@ const state = {
{
blockExplorerUrl: 'https://goerli.etherscan.io',
chainId: '0x5',
iconColor: 'var(--goerli)',
iconColor: 'var(--color-network-goerli-default)',
isATestNetwork: true,
labelKey: 'goerli',
providerType: 'goerli',
@ -40,7 +42,7 @@ const state = {
{
blockExplorerUrl: 'https://sepolia.etherscan.io',
chainId: '0xaa36a7',
iconColor: 'var(--sepolia)',
iconColor: 'var(--color-network-sepolia-default)',
isATestNetwork: true,
labelKey: 'sepolia',
providerType: 'sepolia',
@ -51,7 +53,7 @@ const state = {
{
blockExplorerUrl: '',
chainId: '0x539',
iconColor: 'var(--localhost)',
iconColor: 'var(--color-network-localhost-default)',
isATestNetwork: true,
label: 'Localhost 8545',
providerType: 'rpc',
@ -61,7 +63,7 @@ const state = {
{
blockExplorerUrl: 'https://bscscan.com',
chainId: '0x38',
iconColor: 'var(--localhost)',
iconColor: 'var(--color-network-localhost-default)',
isATestNetwork: false,
label: 'Binance Smart Chain',
providerType: 'rpc',
@ -71,7 +73,7 @@ const state = {
{
blockExplorerUrl: 'https://cchain.explorer.avax.network/',
chainId: '0xa86a',
iconColor: 'var(--localhost)',
iconColor: 'var(--color-network-localhost-default)',
isATestNetwork: false,
label: 'Avalanche',
providerType: 'rpc',
@ -81,7 +83,7 @@ const state = {
{
blockExplorerUrl: 'https://polygonscan.com',
chainId: '0x89',
iconColor: 'var(--localhost)',
iconColor: 'var(--color-network-localhost-default)',
isATestNetwork: false,
label: 'Polygon',
providerType: 'rpc',
@ -142,7 +144,7 @@ const state = {
iconUrl:
'https://assets.coingecko.com/coins/images/12256/thumb/falconswap.png?1598534184',
aggregators: ['CoinGecko', '1inch', 'Lifi'],
occurrences:3,
occurrences: 3,
unlisted: false,
},
'0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f': {
@ -381,8 +383,7 @@ const state = {
from: '0x64a845a5b02460acf8a3d84503b0d68d028b4bb4',
to: '0xaD6D458402F60fD3Bd25163575031ACDce07538D',
value: '0x0',
data:
'0xa9059cbb000000000000000000000000b19ac54efa18cc3a14a5b821bfec73d284bf0c5e0000000000000000000000000000000000000000000000003782dace9d900000',
data: '0xa9059cbb000000000000000000000000b19ac54efa18cc3a14a5b821bfec73d284bf0c5e0000000000000000000000000000000000000000000000003782dace9d900000',
gas: '0xcb28',
gasPrice: '0x77359400',
},
@ -401,8 +402,7 @@ const state = {
from: '0x64a845a5b02460acf8a3d84503b0d68d028b4bb4',
to: '0xaD6D458402F60fD3Bd25163575031ACDce07538D',
value: '0x0',
data:
'0xa9059cbb000000000000000000000000b19ac54efa18cc3a14a5b821bfec73d284bf0c5e0000000000000000000000000000000000000000000000003782dace9d900000',
data: '0xa9059cbb000000000000000000000000b19ac54efa18cc3a14a5b821bfec73d284bf0c5e0000000000000000000000000000000000000000000000003782dace9d900000',
gas: '0xcb28',
gasPrice: '0x77359400',
},
@ -517,8 +517,8 @@ const state = {
maxModeOn: false,
editingTransactionId: null,
toNickname: 'Account 2',
ensResolution: null,
ensResolutionError: '',
domainResolution: null,
domainResolutionError: '',
token: {
address: '0xaD6D458402F60fD3Bd25163575031ACDce07538D',
symbol: 'DAI',
@ -582,8 +582,7 @@ const state = {
chainId: '0x38',
dappSuggestedGasFees: null,
firstRetryBlockNumber: '0x9c2686',
hash:
'0xf45e7a751adfc0fbadccc972816baf33eb34543e52ace51f0f8d0d7f357afdc6',
hash: '0xf45e7a751adfc0fbadccc972816baf33eb34543e52ace51f0f8d0d7f357afdc6',
history: [
{
chainId: '0x38',
@ -595,8 +594,7 @@ const state = {
status: 'unapproved',
time: 1629582710520,
txParams: {
data:
'0xa9059cbb0000000000000000000000004ef2d5a1d056e7c9e8bcdbf2bd9ac0df749a1c2900000000000000000000000000000000000000000000000029a2241af62c0000',
data: '0xa9059cbb0000000000000000000000004ef2d5a1d056e7c9e8bcdbf2bd9ac0df749a1c2900000000000000000000000000000000000000000000000029a2241af62c0000',
from: '0x17f62b1b2407c41c43e14da0699d6b4b0a521548',
gas: '0x2eb27',
gasPrice: '0x12a05f200',
@ -780,8 +778,7 @@ const state = {
blockHash:
'0x30bf5dfa12e460a5d121267c00ba3047a14ba286e0c4fe75fa979010f527cba0',
blockNumber: '9c2688',
data:
'0x00000000000000000000000000000000000000000000000028426c213d688000',
data: '0x00000000000000000000000000000000000000000000000028426c213d688000',
logIndex: '245',
removed: false,
topics: [
@ -798,8 +795,7 @@ const state = {
blockHash:
'0x30bf5dfa12e460a5d121267c00ba3047a14ba286e0c4fe75fa979010f527cba0',
blockNumber: '9c2688',
data:
'0x000000000000000000000000000000000000000000000000006a94d74f430000',
data: '0x000000000000000000000000000000000000000000000000006a94d74f430000',
logIndex: '246',
removed: false,
topics: [
@ -816,8 +812,7 @@ const state = {
blockHash:
'0x30bf5dfa12e460a5d121267c00ba3047a14ba286e0c4fe75fa979010f527cba0',
blockNumber: '9c2688',
data:
'0x000000000000000000000000000000000000000000000000001ff973cafa8000',
data: '0x000000000000000000000000000000000000000000000000001ff973cafa8000',
logIndex: '247',
removed: false,
topics: [
@ -941,8 +936,7 @@ const state = {
submittedTime: 1629582711337,
time: 1629582710520,
txParams: {
data:
'0xa9059cbb0000000000000000000000004ef2d5a1d056e7c9e8bcdbf2bd9ac0df749a1c2900000000000000000000000000000000000000000000000029a2241af62c0000',
data: '0xa9059cbb0000000000000000000000004ef2d5a1d056e7c9e8bcdbf2bd9ac0df749a1c2900000000000000000000000000000000000000000000000029a2241af62c0000',
from: '0x17f62b1b2407c41c43e14da0699d6b4b0a521548',
gas: '0x2eb27',
gasPrice: '0x12a05f200',
@ -980,8 +974,7 @@ const state = {
red: null,
words: [10233480, null],
},
data:
'0x00000000000000000000000000000000000000000000000028426c213d688000',
data: '0x00000000000000000000000000000000000000000000000028426c213d688000',
logIndex: {
length: 1,
negative: 0,
@ -1013,8 +1006,7 @@ const state = {
red: null,
words: [10233480, null],
},
data:
'0x000000000000000000000000000000000000000000000000006a94d74f430000',
data: '0x000000000000000000000000000000000000000000000000006a94d74f430000',
logIndex: {
length: 1,
negative: 0,
@ -1046,8 +1038,7 @@ const state = {
red: null,
words: [10233480, null],
},
data:
'0x000000000000000000000000000000000000000000000000001ff973cafa8000',
data: '0x000000000000000000000000000000000000000000000000001ff973cafa8000',
logIndex: {
length: 1,
negative: 0,
@ -1125,14 +1116,14 @@ const state = {
unapprovedTypedMessages: {},
unapprovedTypedMessagesCount: 0,
keyringTypes: [
'Simple Key Pair',
'HD Key Tree',
'Trezor Hardware',
'Ledger Hardware',
KEYRING_TYPES.IMPORTED,
KEYRING_TYPES.HD_KEY_TREE,
KEYRING_TYPES.TREZOR,
KEYRING_TYPES.LEDGER,
],
keyrings: [
{
type: 'HD Key Tree',
type: KEYRING_TYPES.HD_KEY_TREE,
accounts: [
'0x64a845a5b02460acf8a3d84503b0d68d028b4bb4',
'0xb19ac54efa18cc3a14a5b821bfec73d284bf0c5e',
@ -1224,8 +1215,7 @@ const state = {
to: '0x045c619e4d29bba3b92769508831b681b83d6a96',
value: '0xbca9bce4d98ca3',
},
hash:
'0x2de9256a7c604586f7ecfd87ae9509851e217f588f9f85feed793c54ed2ce0aa',
hash: '0x2de9256a7c604586f7ecfd87ae9509851e217f588f9f85feed793c54ed2ce0aa',
transactionCategory: 'incoming',
},
'0x320a1fd769373578f78570e5d8f56e89bc7bce9657bb5f4c12d8fe790d471bfd': {
@ -1242,8 +1232,7 @@ const state = {
to: '0x045c619e4d29bba3b92769508831b681b83d6a96',
value: '0xcdb08ab4254000',
},
hash:
'0x320a1fd769373578f78570e5d8f56e89bc7bce9657bb5f4c12d8fe790d471bfd',
hash: '0x320a1fd769373578f78570e5d8f56e89bc7bce9657bb5f4c12d8fe790d471bfd',
transactionCategory: 'incoming',
},
'0x8add6c1ea089a8de9b15fa2056b1875360f17916755c88ace9e5092b7a4b1239': {
@ -1260,8 +1249,7 @@ const state = {
to: '0x045c619e4d29bba3b92769508831b681b83d6a96',
value: '0xe6ed27d6668000',
},
hash:
'0x8add6c1ea089a8de9b15fa2056b1875360f17916755c88ace9e5092b7a4b1239',
hash: '0x8add6c1ea089a8de9b15fa2056b1875360f17916755c88ace9e5092b7a4b1239',
transactionCategory: 'incoming',
},
'0x50be62ab1cabd03ff104c602c11fdef7a50f3d73c55006d5583ba97950ab1144': {
@ -1278,8 +1266,7 @@ const state = {
to: '0x045c619e4d29bba3b92769508831b681b83d6a96',
value: '0x63eb89da4ed00000',
},
hash:
'0x50be62ab1cabd03ff104c602c11fdef7a50f3d73c55006d5583ba97950ab1144',
hash: '0x50be62ab1cabd03ff104c602c11fdef7a50f3d73c55006d5583ba97950ab1144',
transactionCategory: 'incoming',
},
},
@ -1512,8 +1499,7 @@ const state = {
from: '0x64a845a5b02460acf8a3d84503b0d68d028b4bb4',
to: '0xaD6D458402F60fD3Bd25163575031ACDce07538D',
value: '0x0',
data:
'0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef40000000000000000000000000000000000000000000000000000000000011170',
data: '0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef40000000000000000000000000000000000000000000000000000000000011170',
gas: '0xea60',
gasPrice: '0x4a817c800',
},
@ -1532,8 +1518,7 @@ const state = {
from: '0x983211ce699ea5ab57cc528086154b6db1ad8e55',
to: '0xaD6D458402F60fD3Bd25163575031ACDce07538D',
value: '0x0',
data:
'0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef40000000000000000000000000000000000000000000000000000000000011170',
data: '0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef40000000000000000000000000000000000000000000000000000000000011170',
gas: '0xea60',
gasPrice: '0x4a817c800',
},

@ -15,7 +15,7 @@ To learn how to contribute to the MetaMask project itself, visit our [Internal D
## Building locally
- Install [Node.js](https://nodejs.org) version 16
- If you are using [nvm](https://github.com/creationix/nvm#installation) (recommended) running `nvm use` will automatically choose the right node version for you.
- If you are using [nvm](https://github.com/nvm-sh/nvm#installing-and-updating) (recommended) running `nvm use` will automatically choose the right node version for you.
- Install [Yarn](https://yarnpkg.com/en/docs/install)
- Install dependencies: `yarn setup` (not the usual install command)
- Copy the `.metamaskrc.dist` file to `.metamaskrc`
@ -61,13 +61,16 @@ You can run the linter by itself with `yarn lint`, and you can automatically fix
### Running E2E Tests
Our e2e test suite can be run on either Firefox or Chrome. In either case, start by creating a test build by running `yarn build:test`.
Our e2e test suite can be run on either Firefox or Chrome.
- Firefox e2e tests can be run with `yarn test:e2e:firefox`.
1. **required** `yarn build:test` to create a test build.
2. run tests, targetting the browser:
* Firefox e2e tests can be run with `yarn test:e2e:firefox`.
* Chrome e2e tests can be run with `yarn test:e2e:chrome`. The `chromedriver` package major version must match the major version of your local Chrome installation. If they don't match, update whichever is behind before running Chrome e2e tests.
- Chrome e2e tests can be run with `yarn test:e2e:chrome`. The `chromedriver` package major version must match the major version of your local Chrome installation. If they don't match, update whichever is behind before running Chrome e2e tests.
#### Running a single e2e test
- Single e2e tests can be run with `yarn test:e2e:single test/e2e/tests/TEST_NAME.spec.js` along with the options below.
Single e2e tests can be run with `yarn test:e2e:single test/e2e/tests/TEST_NAME.spec.js` along with the options below.
```console
--browser Set the browser used; either 'chrome' or 'firefox'.

@ -1853,7 +1853,7 @@
"message": "Richtungspfeil"
},
"lightTheme": {
"message": "Leicht"
"message": "Hell"
},
"likeToImportTokens": {
"message": "Möchtest du diese Token hinzufügen?"

@ -438,21 +438,31 @@
"message": "Beta"
},
"betaHeaderText": {
"message": "This is a BETA version. Please report bugs $1",
"message": "This is a beta version. Please report bugs $1",
"description": "$1 represents the word 'here' in a hyperlink"
},
"betaMetamaskDescription": {
"message": "Trusted by millions, MetaMask is a secure wallet making the world of web3 accessible to all."
},
"betaMetamaskDescriptionDisclaimerHeading": {
"message": "Beta version disclaimer"
},
"betaMetamaskDescriptionExplanation": {
"message": "Use this version to test upcoming features before they’re released. Your use and feedback helps us build the best version of MetaMask possible. Your use of MetaMask Beta is subject to our standard $1 as well as our $2. As a Beta, there may be an increased risk of bugs. By proceeding, you accept and acknowledge these risks, as well as those risks found in our Terms and Beta Terms.",
"message": "This version allows you to test upcoming features before they’re released, which helps make MetaMask even better. As with all beta versions, there may be an increased risk of bugs. MetaMask Beta is subject to our $1 as well as our $2.",
"description": "$1 represents localization item betaMetamaskDescriptionExplanationTermsLinkText. $2 represents localization item betaMetamaskDescriptionExplanationBetaTermsLinkText"
},
"betaMetamaskDescriptionExplanation2": {
"message": "By proceeding, you accept and acknowledge these risks, our $1, and $2.",
"description": "$1 represents localization item betaMetamaskDescriptionExplanationTermsLinkText. $2 represents localization item betaMetamaskDescriptionExplanation2BetaTermsLinkText"
},
"betaMetamaskDescriptionExplanation2BetaTermsLinkText": {
"message": "Beta Terms"
},
"betaMetamaskDescriptionExplanationBetaTermsLinkText": {
"message": "Supplemental Beta Terms"
"message": "supplemental Beta Terms"
},
"betaMetamaskDescriptionExplanationTermsLinkText": {
"message": "Terms"
"message": "standard Terms"
},
"betaMetamaskVersion": {
"message": "MetaMask Beta Version"
@ -461,13 +471,13 @@
"message": "beta portfolio site"
},
"betaTerms": {
"message": "BETA Terms of use"
"message": "Beta Terms of use"
},
"betaWalletCreationSuccessReminder1": {
"message": "MetaMask BETA can’t recover your Secret Recovery Phrase."
"message": "MetaMask Beta can’t recover your Secret Recovery Phrase."
},
"betaWalletCreationSuccessReminder2": {
"message": "MetaMask BETA will never ask you for your Secret Recovery Phrase."
"message": "MetaMask Beta will never ask you for your Secret Recovery Phrase."
},
"betaWelcome": {
"message": "Welcome to MetaMask Beta"
@ -774,6 +784,15 @@
"contractInteraction": {
"message": "Contract interaction"
},
"contractNFT": {
"message": "NFT contract"
},
"contractRequestingAccess": {
"message": "Contract requesting access"
},
"contractRequestingSignature": {
"message": "Contract requesting signature"
},
"contractRequestingSpendingCap": {
"message": "Contract requesting spending cap"
},
@ -1701,6 +1720,12 @@
"message": "Imported",
"description": "status showing that an account has been fully loaded into the keyring"
},
"improvedTokenAllowance": {
"message": "Improved token allowance experience"
},
"improvedTokenAllowanceDescription": {
"message": "Turn this on to go through the improved token allowance experience whenever a dapp requests an ERC20 approve"
},
"inYourSettings": {
"message": "in your Settings"
},
@ -2153,6 +2178,9 @@
"networkName": {
"message": "Network name"
},
"networkNameArbitrum": {
"message": "Arbitrum"
},
"networkNameAvalanche": {
"message": "Avalanche"
},
@ -2168,6 +2196,9 @@
"networkNameGoerli": {
"message": "Goerli"
},
"networkNameOptimism": {
"message": "Optimism"
},
"networkNamePolygon": {
"message": "Polygon"
},
@ -2387,6 +2418,15 @@
"notifications15Title": {
"message": "The Ethereum Merge is here!"
},
"notifications16ActionText": {
"message": "Try it out here"
},
"notifications16Description": {
"message": "We redesigned our token allowance confirmation to help you make more informed decisions."
},
"notifications16Title": {
"message": "Improved token allowance experience"
},
"notifications1Description": {
"message": "MetaMask Mobile users can now swap tokens inside their mobile wallet. Scan the QR code to get the mobile app and start swapping.",
"description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature."
@ -2664,6 +2704,10 @@
"message": "Connect to the $1 Snap.",
"description": "The description for the `wallet_snap_*` permission. $1 is the name of the Snap."
},
"permission_cronjob": {
"message": "Schedule and execute periodic actions.",
"description": "The description for the `snap_cronjob` permission"
},
"permission_customConfirmation": {
"message": "Display a confirmation in MetaMask.",
"description": "The description for the `snap_confirm` permission"
@ -2672,6 +2716,14 @@
"message": "See address, account balance, activity and suggest transactions to approve",
"description": "The description for the `eth_accounts` permission"
},
"permission_ethereumProvider": {
"message": "Access the Ethereum provider.",
"description": "The description for the `endowment:ethereum-provider` permission"
},
"permission_getEntropy": {
"message": "Derive arbitrary keys unique to this snap.",
"description": "The description for the `snap_getEntropy` permission"
},
"permission_longRunning": {
"message": "Run indefinitely.",
"description": "The description for the `endowment:long-running` permission"
@ -2947,6 +2999,9 @@
"message": "By revoking permission, the following $1 will no longer be able to access your $2",
"description": "$1 is either key 'account' or 'contract', and $2 is either a string or link of a given token symbol or name"
},
"revokeSpendingCap": {
"message": "Revoke spending cap for your"
},
"revokeSpendingCapTooltipText": {
"message": "This contract will be unable to spend any more of your current or future tokens."
},
@ -3270,6 +3325,10 @@
"snaps": {
"message": "Snaps"
},
"snapsInsightError": {
"message": "An error occured with $1: $2",
"description": "This is shown when the insight snap throws an error. $1 is the snap name, $2 is the error message."
},
"snapsInsightLoading": {
"message": "Loading transaction insight..."
},
@ -3288,6 +3347,9 @@
"someNetworksMayPoseSecurity": {
"message": "Some networks may pose security and/or privacy risks. Understand the risks before adding & using a network."
},
"somethingIsWrong": {
"message": "Something's gone wrong. Try reloading the page."
},
"somethingWentWrong": {
"message": "Oops! Something went wrong."
},
@ -4089,6 +4151,12 @@
"transactionResubmitted": {
"message": "Transaction resubmitted with estimated gas fee increased to $1 at $2"
},
"transactionSecurityCheck": {
"message": "Transaction security check"
},
"transactionSecurityCheckDescription": {
"message": "Turn this on to enable a third party (OpenSea) to review all your transactions and signature requests and warn you about known malicious requests."
},
"transactionSubmitted": {
"message": "Transaction submitted with estimated gas fee of $1 at $2."
},

@ -2616,7 +2616,7 @@
"description": "The description for the `snap_confirm` permission"
},
"permission_ethereumAccounts": {
"message": "Ver las direcciones de las cuentas permitidas (requerido)",
"message": "Ver las direcciones, saldos, actividad de las cuentas permitidas y sugerir transacciones para aprobar (requerido)",
"description": "The description for the `eth_accounts` permission"
},
"permission_longRunning": {

@ -1,10 +1,90 @@
{
"QRHardwareInvalidTransactionTitle": {
"message": "Errore"
},
"QRHardwareMismatchedSignId": {
"message": "Dati incongruenti. Controlla i dati della transazione."
},
"QRHardwarePubkeyAccountOutOfRange": {
"message": "Non ci sono altri account. Se desideri accedere a un altro account non elencato di seguito, ricollega il tuo portafoglio hardware e selezionalo."
},
"QRHardwareScanInstructions": {
"message": "Posiziona il codice QR davanti alla tua fotocamera. Se lo schermo è sfocato non preoccuparti, non influirà sulla lettura."
},
"QRHardwareSignRequestCancel": {
"message": "Annulla"
},
"QRHardwareSignRequestDescription": {
"message": "Dopo aver firmato con il tuo portafoglio, fai click su 'Ottieni firma' per ricevere la firma"
},
"QRHardwareSignRequestGetSignature": {
"message": "Ottieni firma"
},
"QRHardwareSignRequestSubtitle": {
"message": "Scansiona il QR code con il tuo portafoglio"
},
"QRHardwareSignRequestTitle": {
"message": "Richiedi firma"
},
"QRHardwareUnknownQRCodeTitle": {
"message": "Errore"
},
"QRHardwareUnknownWalletQRCode": {
"message": "Codice QR non valido. Scansiona il codice QR di sincronizzazione del portafoglio hardware."
},
"QRHardwareWalletImporterTitle": {
"message": "Scansiona Codice QR"
},
"QRHardwareWalletSteps1Description": {
"message": "Collega un portafoglio hardware air-gapped che comunica tramite codici QR. I portafogli hardware air-gapped supportati ufficialmente includono:"
},
"QRHardwareWalletSteps1Title": {
"message": "Portafoglio HW basato su QR"
},
"QRHardwareWalletSteps2Description": {
"message": "Ngrave (in arrivo)"
},
"SIWEAddressInvalid": {
"message": "L'indirizzo nella richiesta di accesso non corrisponde all'indirizzo dell'account che stai utilizzando per accedere."
},
"SIWEDomainWarningBody": {
"message": "Il sito Web ($1) ti chiede di accedere al dominio sbagliato. Potrebbe trattarsi di un attacco di phishing.",
"description": "$1 represents the website domain"
},
"SIWELabelExpirationTime": {
"message": "Scade il:"
},
"SIWELabelIssuedAt": {
"message": "Rilasciato a:"
},
"SIWELabelMessage": {
"message": "Messaggio:"
},
"SIWELabelNonce": {
"message": "Nonce:"
},
"SIWELabelNotBefore": {
"message": "Non prima:"
},
"SIWELabelResources": {
"message": "Risorse: $1",
"description": "$1 represents the number of resources"
},
"SIWELabelVersion": {
"message": "Versione:"
},
"SIWESiteRequestSubtitle": {
"message": "Questo sito richiede l'accesso con"
},
"SIWESiteRequestTitle": {
"message": "Richiesta di accesso"
},
"SIWEWarningSubtitle": {
"message": "Per confermare di aver compreso, controlla:"
},
"SIWEWarningTitle": {
"message": "Sei sicuro?"
},
"about": {
"message": "Informazioni"
},
@ -16,6 +96,17 @@
"message": "$1 può avere accesso e spendere al massimo",
"description": "$1 is the url of the site requesting ability to spend"
},
"accessAndSpendNoticeNFT": {
"message": "$1 può accedere e spendere questa risorsa",
"description": "$1 is the url of the site requesting ability to spend"
},
"accessYourWalletWithSRP": {
"message": "Accedi al tuo portafoglio con la tua frase di recupero segreta"
},
"accessYourWalletWithSRPDescription": {
"message": "MetaMask non può recuperare la tua password. Useremo la tua frase di recupero segreta per assicurarci tu sia il proprietario, ripristinare il tuo portafoglio e impostare una nuova password. Innanzitutto, inserisci la frase di recupero segreta che ti è stata data in fase di creazione del tuo portafoglio. $1",
"description": "$1 is the words 'Learn More' from key 'learnMore', separated here so that it can be added as a link"
},
"accessingYourCamera": {
"message": "Accesso alla fotocamera..."
},
@ -25,6 +116,10 @@
"accountName": {
"message": "Nome Account"
},
"accountNameDuplicate": {
"message": "Questo nome è già in uso",
"description": "This is an error message shown when the user enters a new account name that matches an existing account name"
},
"accountOptions": {
"message": "Opzioni Account"
},
@ -40,27 +135,109 @@
"activityLog": {
"message": "log attività"
},
"add": {
"message": "Aggiungi"
},
"addANetwork": {
"message": "Aggiungi una rete"
},
"addANetworkManually": {
"message": "Aggungi manualmente una rete"
},
"addANickname": {
"message": "Aggiungo un nickname"
},
"addAcquiredTokens": {
"message": "Aggiungi i token che hai acquistato usando MetaMask"
},
"addAlias": {
"message": "Aggiungi alias"
},
"addBlockExplorer": {
"message": "Aggiungi un block explorer"
},
"addContact": {
"message": "Aggiungi contatto"
},
"addCustomToken": {
"message": "Aggiungi token personalizzato"
},
"addCustomTokenByContractAddress": {
"message": "Non trovi un token? Puoi aggiungere qualsiasi token incollando il suo indirizzo. L'indirizzo del contratto del Token può essere trovato su $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Ciò consentirà a questa rete di essere utilizzata all'interno di MetaMask."
},
"addEthereumChainConfirmationRisks": {
"message": "MetaMask non verifica le reti personalizzate."
},
"addEthereumChainConfirmationRisksLearnMore": {
"message": "Maggiori informazioni su $1.",
"description": "$1 is a link with text that is provided by the 'addEthereumChainConfirmationRisksLearnMoreLink' key"
},
"addEthereumChainConfirmationRisksLearnMoreLink": {
"message": "truffe e rischi per la sicurezza della rete",
"description": "Link text for the 'addEthereumChainConfirmationRisksLearnMore' translation key"
},
"addEthereumChainConfirmationTitle": {
"message": "Consenti a questo sito di aggiungere una rete?"
},
"addFriendsAndAddresses": {
"message": "Aggiungi amici e indirizzi di cui ti fidi"
},
"addFromAListOfPopularNetworks": {
"message": "Aggiungi da un elenco di reti popolari o aggiungi una rete manualmente. Interagisci solo con le entità di cui ti fidi."
},
"addMemo": {
"message": "Aggiungi memo"
},
"addMoreNetworks": {
"message": "aggiungi più reti manualmente"
},
"addNetwork": {
"message": "Aggiungi Rete"
},
"addNetworkTooltipWarning": {
"message": "Questarete si basa su terze parti. La connessione potrebbe essere meno affidabile o consentire a terzi di tracciare le tue attività. $1",
"description": "$1 is Learn more link"
},
"addSuggestedTokens": {
"message": "Aggiungi Token Suggeriti"
},
"addToken": {
"message": "Aggiungi Token"
},
"address": {
"message": "Indirizzo"
},
"addressBookIcon": {
"message": "Icona rubrica indirizzo"
},
"advanced": {
"message": "Avanzate"
},
"advancedBaseGasFeeToolTip": {
"message": "Quando la tua transazione viene inclusa nel blocco, ogni differenza tra la tua offerta massima di gas e il gas effettivamente utilizzato viene restituita a te. Il totale viene calcolato come offerta massima di gas (in GEWI) * limite di gas."
},
"advancedGasFeeDefaultOptIn": {
"message": "Salva queste $1 come mie preferite per \"Avanzate\""
},
"advancedGasFeeDefaultOptOut": {
"message": "Utilizzare sempre questi valori e l'impostazione avanzata come predefiniti."
},
"advancedGasFeeModalTitle": {
"message": "Tariffa gas avanzata"
},
"advancedGasPriceTitle": {
"message": "Prezzo gas"
},
"advancedOptions": {
"message": "Opzioni Avanzate"
},
"advancedPriorityFeeToolTip": {
"message": "La commissione di priorità avanzata (nota anche come \"suggerimento del minatore\") va direttamente ai minatori e li incentiva a dare la priorità alla tua commissione di transazione."
},
"affirmAgree": {
"message": "Acconsento"
},
@ -82,9 +259,17 @@
"alerts": {
"message": "Avvisi"
},
"allOfYour": {
"message": "Tutti i tuoi $1",
"description": "$1 is the symbol or name of the token that the user is approving spending"
},
"allowExternalExtensionTo": {
"message": "Permetti a questa estensione di:"
},
"allowSpendToken": {
"message": "Dai il permesso di spendere tuoi $1?",
"description": "$1 is the symbol of the token that are requesting to spend"
},
"allowThisSiteTo": {
"message": "Permetti a questo sito di:"
},
@ -96,7 +281,7 @@
"message": "Importo"
},
"appDescription": {
"message": "Ethereum Browser Extension",
"message": "Estensione Browser Ethereum",
"description": "The description of the application"
},
"appName": {
@ -114,6 +299,19 @@
"approve": {
"message": "Approva"
},
"approveAllTokensTitle": {
"message": "Consenti l'accesso e il trasferimento di tutti i tuoi $1?",
"description": "$1 is the symbol of the token for which the user is granting approval"
},
"approveAndInstall": {
"message": "Approva & installa"
},
"approveAndUpdate": {
"message": "Approva & aggiorna"
},
"approveButtonText": {
"message": "Approva"
},
"approveSpendLimit": {
"message": "Approva limite di spesa per $1",
"description": "The token symbol that is being approved"
@ -121,9 +319,28 @@
"approved": {
"message": "Approvato"
},
"approvedAmountWithColon": {
"message": "Importo approvato:"
},
"approvedAsset": {
"message": "Asset approvato"
},
"approvedOn": {
"message": "Approvato il $1",
"description": "$1 is the approval date for a permission"
},
"areYouSure": {
"message": "Sei sicuro?"
},
"assetOptions": {
"message": "Opzioni asset"
},
"assets": {
"message": "Patrimonio"
},
"attemptSendingAssets": {
"message": "Se si tenta di inviare risorse direttamente da una rete all'altra, ciò potrebbe comportare una perdita permanente della risorca coinvolta. Assicurati di usare un bridge."
},
"attemptToCancel": {
"message": "Tentativo di Annullamento?"
},
@ -164,27 +381,98 @@
"message": "Esegui il backup ora"
},
"balance": {
"message": "Bilancio:"
"message": "Bilancio"
},
"balanceOutdated": {
"message": "Il bilancio può essere non aggiornato"
},
"baseFee": {
"message": "Commissioni di base"
},
"basic": {
"message": "Base"
},
"beCareful": {
"message": "Presta attenzione"
},
"betaMetamaskDescription": {
"message": "Scelto da milioni di persone, MetaMask è un portafoglio sicuro che rende il mondo del web3 accessibile a tutti."
},
"betaMetamaskDescriptionExplanation": {
"message": "Usa questa versione per testare le funzionalità imminenti prima che vengano rilasciate. Il tuo utilizzo e il tuo feedback ci aiutano a creare la migliore versione possibile di MetaMask. L'utilizzo di MetaMask Beta è soggetto al nostro standard $1 e al nostro $2. Come versione beta, potrebbe esserci un aumento del rischio di bug. Procedendo, accetti e riconosci questi rischi, così come i rischi che si trovano nei nostri Termini e Termini beta.",
"description": "$1 represents localization item betaMetamaskDescriptionExplanationTermsLinkText. $2 represents localization item betaMetamaskDescriptionExplanationBetaTermsLinkText"
},
"betaMetamaskDescriptionExplanationBetaTermsLinkText": {
"message": "Termini beta supplementari"
},
"betaMetamaskDescriptionExplanationTermsLinkText": {
"message": "Termini"
},
"betaMetamaskVersion": {
"message": "MetaMask Versione Beta"
},
"betaWelcome": {
"message": "Benvenuto in MetaMask Beta"
},
"blockExplorerAccountAction": {
"message": "Profilo",
"description": "This is used with viewOnEtherscan and viewInExplorer e.g View Account in Explorer"
},
"blockExplorerUrl": {
"message": "Block Explorer"
"message": "URL Block Explorer"
},
"blockExplorerUrlDefinition": {
"message": "L'URL usato come Block Explorer per questa rete."
},
"blockExplorerView": {
"message": "Visualizza account su $1",
"description": "$1 replaced by URL for custom block explorer"
},
"blockies": {
"message": "Blocchi"
},
"browserNotSupported": {
"message": "Il tuo Browser non è supportato..."
},
"buildContactList": {
"message": "Costruisci la tua lista contatti"
},
"builtAroundTheWorld": {
"message": "MetaMask è progettato e costruito in tutto il mondo."
},
"busy": {
"message": "Occupato"
},
"buy": {
"message": "Compra"
},
"buyAsset": {
"message": "Compra $1",
"description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase"
},
"buyCryptoWithCoinbasePay": {
"message": "Compra $1 con Coinbase Pay",
"description": "$1 represents the crypto symbol to be purchased"
},
"buyCryptoWithCoinbasePayDescription": {
"message": "Puoi facilmente acquistare o trasferire criptovalute con il tuo account Coinbase.",
"description": "$1 represents the crypto symbol to be purchased"
},
"buyCryptoWithMoonPay": {
"message": "Compra $1 con MoonPay",
"description": "$1 represents the cypto symbol to be purchased"
},
"buyCryptoWithMoonPayDescription": {
"message": "MoonPay supporta metodi di pagamento popolari, incluso Visa, Mastercard, Apple / Google / Samsung Pay e bonifici bancari in 145+ paesi. I Token vengono depositati nel tuo account MetaMask."
},
"buyCryptoWithTransak": {
"message": "Compra $1 con Transak",
"description": "$1 represents the cypto symbol to be purchased"
},
"buyCryptoWithTransakDescription": {
"message": "Transak supporta carte di credito e debito, Apple Pay, MobiKwik e bonifici bancari (in base alla località) in 100+ paesi. Deposita $1 direttamente nel tuo account MetaMask.",
"description": "$1 represents the crypto symbol to be purchased"
},
"buyWithWyre": {
"message": "Compra $1 con Wyre"
},
@ -197,24 +485,82 @@
"cancel": {
"message": "Annulla"
},
"cancelEdit": {
"message": "Annulla modifica"
},
"cancelPopoverTitle": {
"message": "Annulla transazione"
},
"cancelSpeedUp": {
"message": "annulla o velocizza una transazione."
},
"cancelSpeedUpLabel": {
"message": "Questa commissione di gas $1 l'originale.",
"description": "$1 is text 'replace' in bold"
},
"cancelSpeedUpTransactionTooltip": {
"message": "Per $1 una transazione la commissione di gas deve crescere almeno del 10% per essere riconosciuto dalla rete.",
"description": "$1 is string 'cancel' or 'speed up'"
},
"cancelSwapForFee": {
"message": "Annulla scambio per ~$1",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
},
"cancelSwapForFree": {
"message": "Annulla scambio gratuitamente"
},
"cancellationGasFee": {
"message": "Costo di Annullamento in Gas"
},
"cancelled": {
"message": "Annullata"
},
"chainIdDefinition": {
"message": "Il Chain ID utilizzato per firmare le transazioni per questa rete."
},
"chainIdExistsErrorMsg": {
"message": "Questo Chain ID al momento è usato dalla rete $1."
},
"chainListReturnedDifferentTickerSymbol": {
"message": "La rete con chain ID $1 può utilizzare un simbolo diverso ($2) da quello che hai inserito. Si prega di verificare prima di continuare.",
"description": "$1 is the chain id currently entered in the network form and $2 is the return value of nativeCurrency.symbol from chainlist.network"
},
"chromeRequiredForHardwareWallets": {
"message": "Devi usare MetaMask con Google Chrome per connettere il tuo Portafoglio Hardware"
},
"clickToConnectLedgerViaWebHID": {
"message": "Clicca qui per connettere il tuo Ledger tramite WebHID",
"description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid"
},
"clickToManuallyAdd": {
"message": "Clicca qui per aggiungere manualmente i token."
},
"clickToRevealSeed": {
"message": "Clicca qui per rivelare la tua frase segreta"
},
"close": {
"message": "Chiudi"
},
"collectibleAddFailedMessage": {
"message": "Non è possibile aggiungere questo NFT poiché i dettagli della proprietà non corrispondono. Assicurati di aver inserito le informazioni corrette."
},
"collectibleAddressError": {
"message": "Questo token è un NFT. Aggiungilo su $1",
"description": "$1 is a clickable link with text defined by the 'importNFTPage' key"
},
"confirm": {
"message": "Conferma"
},
"confirmPageDialogSetApprovalForAll": {
"message": "Stai concedendo l'accesso a $1, inclusi quelli che potresti possedere in futuro. L'ente a cui stai concedendo l'accesso potrà trasferire NFT dal tuo portafoglio in qualsiasi momento senza chiederti conferma fino a quando non revocherai questa approvazione. $2",
"description": "$1 and $2 are bolded translations 'confirmPageDialogSetApprovalForAllPlaceholder1' and 'confirmPageDialogSetApprovalForAllPlaceholder2'"
},
"confirmPageDialogSetApprovalForAllPlaceholder1": {
"message": "tutti gli NFT in questo contratto"
},
"confirmPageDialogSetApprovalForAllPlaceholder2": {
"message": "Procedi con cautela."
},
"confirmPassword": {
"message": "Conferma Password"
},
@ -224,6 +570,15 @@
"confirmed": {
"message": "Confermata"
},
"confusableUnicode": {
"message": "'$1' è simile a '$2'."
},
"confusableZeroWidthUnicode": {
"message": "Trovato carattere a larghezza zero."
},
"confusingEnsDomain": {
"message": "Abbiamo rilevato un carattere confondibile nel nome ENS. Controlla il nome ENS per evitare una potenziale truffa."
},
"congratulations": {
"message": "Congratulazioni"
},
@ -283,6 +638,10 @@
"message": "$1 non è connesso ad alcun sito.",
"description": "$1 is the account name"
},
"connectedSnapSites": {
"message": "$1 snap è collegato a questi siti. Hanno accesso alle autorizzazioni sopra elencate.",
"description": "$1 represents the name of the snap"
},
"connecting": {
"message": "Connessione..."
},
@ -295,6 +654,9 @@
"connectingToMainnet": {
"message": "Connessione alla Rete Ethereum Principale"
},
"connectingToSepolia": {
"message": "Connessione alla Rete di test Sepolia"
},
"contactUs": {
"message": "Contattaci!"
},
@ -304,15 +666,51 @@
"continue": {
"message": "Continua"
},
"continueToCoinbasePay": {
"message": "Continua su Coinbase Pay"
},
"continueToMoonPay": {
"message": "Continua su MoonPay"
},
"continueToTransak": {
"message": "Continua su Transak"
},
"continueToWyre": {
"message": "Continua su Wyre"
},
"contract": {
"message": "Contratto"
},
"contractAddress": {
"message": "Indirizzo contratto"
},
"contractAddressError": {
"message": "Stai inviando i token all'indirizzo del contratto del token. Ciò potrebbe comportare la perdita di questi token."
},
"contractDeployment": {
"message": "Distribuzione Contratto"
},
"contractDescription": {
"message": "Per proteggerti dai truffatori, prenditi un momento per verificare i dettagli del contratto."
},
"contractInteraction": {
"message": "Interazione Contratto"
},
"contractRequestingSpendingCap": {
"message": "Il contratto richiede un limite di spesa"
},
"contractTitle": {
"message": "Dettagli contratto"
},
"contractToken": {
"message": "Token contratto"
},
"convertTokenToNFTDescription": {
"message": "Abbiamo rilevato che questa risorsa è un NFT. MetaMask ora ha il supporto nativo completo per NFT. Vuoi rimuoverlo dal tuo elenco di token e aggiungerlo come NFT?"
},
"convertTokenToNFTExistDescription": {
"message": "Abbiamo rilevato che questa risorsa è stata aggiunta come NFT. Vuoi rimuoverlo dal tuo elenco di token?"
},
"copiedExclamation": {
"message": "Copiato!"
},
@ -322,6 +720,9 @@
"copyPrivateKey": {
"message": "Questa è la tua chiave privata (clicca per copiare)"
},
"copyRawTransactionData": {
"message": "Copia i dati grezzi della transazione"
},
"copyToClipboard": {
"message": "Copia negli appunti"
},
@ -337,12 +738,21 @@
"createAccount": {
"message": "Crea Account"
},
"createNewWallet": {
"message": "Crea un nuovo portafoglio"
},
"createPassword": {
"message": "Crea Password"
},
"currencyConversion": {
"message": "Conversione Moneta"
},
"currencySymbol": {
"message": "Simbolo moneta"
},
"currencySymbolDefinition": {
"message": "Il simbolo del ticker visualizzato per la valuta di questa rete."
},
"currentAccountNotConnected": {
"message": "Il tuo account corrente non è connesso"
},
@ -352,15 +762,72 @@
"currentLanguage": {
"message": "Lingua Corrente"
},
"currentTitle": {
"message": "Corrente:"
},
"currentlyUnavailable": {
"message": "Non disponibile su questa Rete"
},
"curveHighGasEstimate": {
"message": "Grafico di stima del gas aggressivo"
},
"curveLowGasEstimate": {
"message": "Grafico della stima del gas basso"
},
"curveMediumGasEstimate": {
"message": "Grafico della stima del gas di mercato"
},
"custom": {
"message": "Avanzate"
},
"customContentSearch": {
"message": "Cerca una rete aggiunta in precedenza"
},
"customGasSettingToolTipMessage": {
"message": "Usa $1 per personalizzare il prezzo del gas. Questo può creare confusione se non hai familiarità. Interagisci a tuo rischio.",
"description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight"
},
"customSpendLimit": {
"message": "Limite Spesa Personalizzato"
},
"customSpendingCap": {
"message": "Custom spending cap"
},
"customToken": {
"message": "Token Personalizzato"
},
"customTokenWarningInNonTokenDetectionNetwork": {
"message": "Il rilevamento dei token non è ancora disponibile su questa rete. Importa il token manualmente e assicurati che sia corretto. Ulteriori informazioni su $1"
},
"customTokenWarningInTokenDetectionNetwork": {
"message": "Prima di importare manualmente un token, assicurati sia attendibile. Ulteriori informazioni su $ 1"
},
"customTokenWarningInTokenDetectionNetworkWithTDOFF": {
"message": "Assicurati di fidarti di un token prima di importarlo. Scopri come evitare $1. Puoi anche abilitare il rilevamento dei token $2."
},
"customerSupport": {
"message": "servizio clienti"
},
"dappSuggested": {
"message": "Sito suggerito"
},
"dappSuggestedGasSettingToolTipMessage": {
"message": "$1 ha suggerito questo prezzo.",
"description": "$1 is url for the dapp that has suggested gas settings"
},
"dappSuggestedShortLabel": {
"message": "Sito"
},
"dappSuggestedTooltip": {
"message": "$1 ha consigliato questo prezzo.",
"description": "$1 represents the Dapp's origin"
},
"darkTheme": {
"message": "Scuro"
},
"dataBackupSeemsCorrupt": {
"message": "Impossibile ripristinare i tuoi dati. Il file sembra essere corrotto."
},
"decimal": {
"message": "Precisione Decimali"
},
@ -399,9 +866,32 @@
"deleteNetworkDescription": {
"message": "Sei sicuro di voler eliminare questa rete?"
},
"depositCrypto": {
"message": "Deposita $1",
"description": "$1 represents the crypto symbol to be purchased"
},
"deprecatedTestNetworksLink": {
"message": "Scopri di più"
},
"deprecatedTestNetworksMsg": {
"message": "A causa delle modifiche al protocollo di Ethereum: le reti di test Rinkeby, Ropsten e Kovan potrebbero non funzionare in modo affidabile e saranno presto rimosse."
},
"description": {
"message": "Descrizione"
},
"details": {
"message": "Dettagli"
},
"directDepositCrypto": {
"message": "Deposito diretto $1"
},
"directDepositCryptoExplainer": {
"message": "Se hai già $1, il modo più rapido per ottenere $1 nel tuo nuovo portafoglio tramite deposito diretto."
},
"disabledGasOptionToolTipMessage": {
"message": "“$1” è disabilitato perché non soddisfa la maggiorazione minima del 10% rispetto al canone gas originario.",
"description": "$1 is gas estimate type which can be market or aggressive"
},
"disconnect": {
"message": "Disconnetti"
},
@ -420,15 +910,30 @@
"dismiss": {
"message": "Ignora"
},
"dismissReminderDescriptionField": {
"message": "Attiva questa opzione per ignorare il messaggio di promemoria del backup della frase di ripristino segreta. Ti consigliamo vivamente di eseguire il backup della tua frase di recupero segreta per evitare la perdita di fondi"
},
"dismissReminderField": {
"message": "Ignora il promemoria di backup della frase di ripristino segreta"
},
"domain": {
"message": "Dominio"
},
"done": {
"message": "Finito"
},
"dontShowThisAgain": {
"message": "Non mostrare di nuovo"
},
"downArrow": {
"message": "Freccia in giù"
},
"downloadGoogleChrome": {
"message": "Scarica Google Chrome"
},
"downloadNow": {
"message": "Scarica ora"
},
"downloadSecretBackup": {
"message": "Scarica questa Frase di Backup Segreta e tienila al sicuro in un hard disk o supporto di memorizzazione esterno criptato."
},
@ -441,9 +946,64 @@
"edit": {
"message": "Modifica"
},
"editANickname": {
"message": "Modifica nickname"
},
"editAddressNickname": {
"message": "Modifica indirizzo nickname"
},
"editCancellationGasFeeModalTitle": {
"message": "Modifica la tassa di cancellazione del gas"
},
"editContact": {
"message": "Modifica contatto"
},
"editGasEducationButtonText": {
"message": "Come devo scegliere?"
},
"editGasEducationHighExplanation": {
"message": "Questo è il migliore per le transazioni sensibili al tempo (come gli swap) in quanto aumenta la probabilità di una transazione riuscita. Se uno Swap impiega troppo tempo per l'elaborazione, potrebbe non riuscire e comportare la perdita di parte della tariffa del gas."
},
"editGasEducationLowExplanation": {
"message": "Una tariffa gas inferiore dovrebbe essere utilizzata solo quando il tempo di elaborazione è meno importante. Commissioni più basse rendono difficile prevedere quando (o se) la tua transazione avrà esito positivo."
},
"editGasEducationMediumExplanation": {
"message": "Una commissione gas media va bene per l'invio, il prelievo o altre transazioni non sensibili al fattore tempo. Molto spesso questa impostazione risulterà in una transazione riuscita."
},
"editGasEducationModalIntro": {
"message": "La scelta della giusta commissione per il gas dipende dal tipo di transazione e da quanto è importante per te."
},
"editGasEducationModalTitle": {
"message": "Come scegliere?"
},
"editGasFeeModalTitle": {
"message": "Modificare la tariffa del gas"
},
"editGasHigh": {
"message": "Alta"
},
"editGasLimitOutOfBounds": {
"message": "Il limite di gas deve essere almeno $1"
},
"editGasLimitOutOfBoundsV2": {
"message": "Il limite del gas deve essere maggiore di $1 e minore di $2",
"description": "$1 is the minimum limit for gas and $2 is the maximum limit"
},
"editGasLimitTooltip": {
"message": "Il limite del gas è l'unità massima di gas che sei disposto a utilizzare. Le unità di gas sono un moltiplicatore per “Commissione massima priorità“ e “Commissione massima“."
},
"editGasLow": {
"message": "Bassa"
},
"editGasMaxBaseFeeGWEIImbalance": {
"message": "La tariffa base massima non può essere inferiore alla tariffa prioritaria"
},
"editGasMaxBaseFeeHigh": {
"message": "La tariffa base massima è superiore al necessario"
},
"editGasMaxBaseFeeLow": {
"message": "La tariffa base massima è bassa per le condizioni di rete attuali"
},
"editPermission": {
"message": "Modifica Permessi"
},

File diff suppressed because it is too large Load Diff

@ -1,4 +1,4 @@
{
"description": "THIS IS THE METAMASK EXTENSION BETA, INTENDED FOR BETA TESTING",
"name": "MetaMask BETA"
"name": "MetaMask Beta"
}

Before

Width:  |  Height:  |  Size: 196 B

After

Width:  |  Height:  |  Size: 196 B

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" id="icon-user-cirlce-add-filled" viewBox="0 0 512 512">
<path d="m448 70c-14-17-36-27-60-27-23 0-43 9-58 24-9 9-15 20-19 31-3 8-4 17-4 26 0 15 4 30 12 42 4 7 9 13 15 18 14 13 33 21 54 21 9 0 18-1 25-4 19-6 35-19 45-35 4-7 7-15 9-23 2-6 2-13 2-19 0-21-8-40-21-54z m-30 69l-15 0 0 16c0 8-7 15-15 15-8 0-15-7-15-15l0-16-15 0c-9 0-15-7-15-15 0-9 6-16 15-16l15 0 0-14c0-8 7-15 15-15 8 0 15 7 15 15l0 14 15 0c9 0 16 7 16 16 0 8-7 15-16 15z m31 127c0-26-5-52-15-75-6 4-13 8-21 10-2 1-4 1-6 2 7 19 12 41 12 63 0 47-20 90-50 121-6-7-14-14-23-20-55-37-145-37-201 0-8 6-16 13-22 20-31-31-50-74-50-121 0-95 78-173 173-173 22 0 43 5 63 12 0-2 1-4 2-7 2-7 6-14 10-20-23-10-49-15-75-15-112 0-203 91-203 203 0 59 25 112 65 149 0 0 0 0 0 1 2 2 5 3 7 5 1 1 2 2 3 3 4 3 8 6 12 9 1 1 2 2 4 3 4 2 8 5 12 7 1 1 3 2 4 3 4 2 9 4 13 6 2 0 3 1 5 2 4 2 9 3 13 5 2 0 4 1 5 1 5 2 10 3 15 4 1 1 3 1 4 1 6 1 12 2 18 3 0 0 1 0 2 0 7 1 14 1 21 1 7 0 14 0 20-1 1 0 2 0 3 0 6-1 11-2 17-3 1 0 3-1 5-1 4-1 9-2 14-4 2 0 3-1 5-1 5-2 9-3 13-5 2-1 4-2 5-2 5-2 9-4 13-6 2-1 3-2 5-3 4-3 8-5 12-7 1-1 2-2 4-3 4-3 8-6 11-9 2-1 3-2 4-3 2-2 4-3 6-5 0-1 0-1 0-1 41-37 66-90 66-149z m-203-103c-42 0-76 34-76 76 0 42 32 75 75 76 0 0 1 0 2 0 0 0 1 0 1 0 0 0 0 0 0 0 42-1 74-34 74-76 0-42-34-76-76-76z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

@ -127,6 +127,10 @@ chrome.runtime.onMessage.addListener(() => {
return false;
});
chrome.runtime.onStartup.addListener(() => {
globalThis.isFirstTimeProfileLoaded = true;
});
/*
* This content script is injected programmatically because
* MAIN world injection does not work properly via manifest

@ -15,6 +15,7 @@ import {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_FULLSCREEN,
EXTENSION_MESSAGES,
PLATFORM_FIREFOX,
} from '../../shared/constants/app';
import { SECOND } from '../../shared/constants/time';
@ -25,6 +26,7 @@ import {
EVENT_NAMES,
TRAITS,
} from '../../shared/constants/metametrics';
import { checkForLastErrorAndLog } from '../../shared/modules/browser-runtime.utils';
import { isManifestV3 } from '../../shared/modules/mv3.utils';
import { maskObject } from '../../shared/modules/object.utils';
import migrations from './migrations';
@ -79,7 +81,7 @@ const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore();
let versionedData;
if (inTest || process.env.METAMASK_DEBUG) {
global.metamaskGetState = localStore.get.bind(localStore);
global.stateHooks.metamaskGetState = localStore.get.bind(localStore);
}
const phishingPageUrl = new URL(process.env.PHISHING_WARNING_PAGE_URL);
@ -88,19 +90,73 @@ const ONE_SECOND_IN_MILLISECONDS = 1_000;
// Timeout for initializing phishing warning page.
const PHISHING_WARNING_PAGE_TIMEOUT = ONE_SECOND_IN_MILLISECONDS;
const ACK_KEEP_ALIVE_MESSAGE = 'ACK_KEEP_ALIVE_MESSAGE';
const WORKER_KEEP_ALIVE_MESSAGE = 'WORKER_KEEP_ALIVE_MESSAGE';
/**
* In case of MV3 we attach a "onConnect" event listener as soon as the application is initialised.
* Reason is that in case of MV3 a delay in doing this was resulting in missing first connect event after service worker is re-activated.
*
* @param remotePort
*/
const initApp = async (remotePort) => {
browser.runtime.onConnect.removeListener(initApp);
await initialize(remotePort);
log.info('MetaMask initialization complete.');
};
/**
* Sends a message to the dapp(s) content script to signal it can connect to MetaMask background as
* the backend is not active. It is required to re-connect dapps after service worker re-activates.
* For non-dapp pages, the message will be sent and ignored.
*/
const sendReadyMessageToTabs = async () => {
const tabs = await browser.tabs
.query({
/**
* Only query tabs that our extension can run in. To do this, we query for all URLs that our
* extension can inject scripts in, which is by using the "<all_urls>" value and __without__
* the "tabs" manifest permission. If we included the "tabs" permission, this would also fetch
* URLs that we'd not be able to inject in, e.g. chrome://pages, chrome://extension, which
* is not what we'd want.
*
* You might be wondering, how does the "url" param work without the "tabs" permission?
*
* @see {@link https://bugs.chromium.org/p/chromium/issues/detail?id=661311#c1}
* "If the extension has access to inject scripts into Tab, then we can return the url
* of Tab (because the extension could just inject a script to message the location.href)."
*/
url: '<all_urls>',
windowType: 'normal',
})
.then((result) => {
checkForLastErrorAndLog();
return result;
})
.catch(() => {
checkForLastErrorAndLog();
});
/** @todo we should only sendMessage to dapp tabs, not all tabs. */
for (const tab of tabs) {
browser.tabs
.sendMessage(tab.id, {
name: EXTENSION_MESSAGES.READY,
})
.then(() => {
checkForLastErrorAndLog();
})
.catch(() => {
// An error may happen if the contentscript is blocked from loading,
// and thus there is no runtime.onMessage handler to listen to the message.
checkForLastErrorAndLog();
});
}
};
if (isManifestV3) {
browser.runtime.onConnect.addListener(initApp);
sendReadyMessageToTabs();
} else {
// initialization flow
initialize().catch(log.error);
@ -438,7 +494,15 @@ function setupController(initState, initLangCode, remoteSourcePort) {
// Message below if captured by UI code in app/scripts/ui.js which will trigger UI initialisation
// This ensures that UI is initialised only after background is ready
// It fixes the issue of blank screen coming when extension is loaded, the issue is very frequent in MV3
remotePort.postMessage({ name: 'CONNECTION_READY' });
remotePort.postMessage({ name: EXTENSION_MESSAGES.CONNECTION_READY });
// If we get a WORKER_KEEP_ALIVE message, we respond with an ACK
remotePort.onMessage.addListener((message) => {
if (message.name === WORKER_KEEP_ALIVE_MESSAGE) {
// To test un-comment this line and wait for 1 minute. An error should be shown on MetaMask UI.
remotePort.postMessage({ name: ACK_KEEP_ALIVE_MESSAGE });
}
});
}
if (processName === ENVIRONMENT_TYPE_POPUP) {
@ -742,7 +806,7 @@ browser.runtime.onInstalled.addListener(({ reason }) => {
});
function setupSentryGetStateGlobal(store) {
global.sentryHooks.getSentryState = function () {
global.stateHooks.getSentryState = function () {
const fullState = store.getState();
const debugState = maskObject({ metamask: fullState }, SENTRY_STATE);
return {

@ -5,6 +5,8 @@ import browser from 'webextension-polyfill';
import PortStream from 'extension-port-stream';
import { obj as createThoughStream } from 'through2';
import { EXTENSION_MESSAGES, MESSAGE_TYPE } from '../../shared/constants/app';
import { checkForLastError } from '../../shared/modules/browser-runtime.utils';
import { isManifestV3 } from '../../shared/modules/mv3.utils';
import shouldInjectProvider from '../../shared/modules/provider-injection';
@ -44,9 +46,6 @@ let legacyExtMux,
legacyPagePublicConfigChannel,
notificationTransformStream;
const WORKER_KEEP_ALIVE_INTERVAL = 1000;
const WORKER_KEEP_ALIVE_MESSAGE = 'WORKER_KEEP_ALIVE_MESSAGE';
const phishingPageUrl = new URL(process.env.PHISHING_WARNING_PAGE_URL);
let phishingExtChannel,
@ -82,6 +81,51 @@ function injectScript(content) {
}
}
/**
* SERVICE WORKER LOGIC
*/
const WORKER_KEEP_ALIVE_INTERVAL = 1000;
const WORKER_KEEP_ALIVE_MESSAGE = 'WORKER_KEEP_ALIVE_MESSAGE';
const TIME_45_MIN_IN_MS = 45 * 60 * 1000;
/**
* Don't run the keep-worker-alive logic for JSON-RPC methods called on initial load.
* This is to prevent the service worker from being kept alive when accounts are not
* connected to the dapp or when the user is not interacting with the extension.
* The keep-alive logic should not work for non-dapp pages.
*/
const IGNORE_INIT_METHODS_FOR_KEEP_ALIVE = [
MESSAGE_TYPE.GET_PROVIDER_STATE,
MESSAGE_TYPE.SEND_METADATA,
];
let keepAliveInterval;
let keepAliveTimer;
/**
* Running this method will ensure the service worker is kept alive for 45 minutes.
* The first message is sent immediately and subsequent messages are sent at an
* interval of WORKER_KEEP_ALIVE_INTERVAL.
*/
const runWorkerKeepAliveInterval = () => {
clearTimeout(keepAliveTimer);
keepAliveTimer = setTimeout(() => {
clearInterval(keepAliveInterval);
}, TIME_45_MIN_IN_MS);
clearInterval(keepAliveInterval);
browser.runtime.sendMessage({ name: WORKER_KEEP_ALIVE_MESSAGE });
keepAliveInterval = setInterval(() => {
if (browser.runtime.id) {
browser.runtime.sendMessage({ name: WORKER_KEEP_ALIVE_MESSAGE });
}
}, WORKER_KEEP_ALIVE_INTERVAL);
};
/**
* PHISHING STREAM LOGIC
*/
@ -93,6 +137,10 @@ function setupPhishingPageStreams() {
target: PHISHING_WARNING_PAGE,
});
if (isManifestV3) {
runWorkerKeepAliveInterval();
}
// create and connect channel muxers
// so we can handle the channels individually
phishingPageMux = new ObjectMultiplex();
@ -142,6 +190,9 @@ const setupPhishingExtStreams = () => {
error,
),
);
// eslint-disable-next-line no-use-before-define
phishingExtPort.onDisconnect.addListener(onDisconnectDestroyPhishingStreams);
};
/** Destroys all of the phishing extension streams */
@ -153,19 +204,54 @@ const destroyPhishingExtStreams = () => {
phishingExtChannel.removeAllListeners();
phishingExtChannel.destroy();
phishingExtStream = null;
};
/**
* Resets the extension stream with new streams to channel with the phishing page streams,
* and creates a new event listener to the reestablished extension port.
* This listener destroys the phishing extension streams when the extension port is disconnected,
* so that streams may be re-established later the phishing extension port is reconnected.
*/
const resetPhishingStreamAndListeners = () => {
phishingExtPort.onDisconnect.removeListener(resetPhishingStreamAndListeners);
const onDisconnectDestroyPhishingStreams = () => {
const err = checkForLastError();
phishingExtPort.onDisconnect.removeListener(
onDisconnectDestroyPhishingStreams,
);
destroyPhishingExtStreams();
setupPhishingExtStreams();
phishingExtPort.onDisconnect.addListener(resetPhishingStreamAndListeners);
/**
* If an error is found, reset the streams. When running two or more dapps, resetting the service
* worker may cause the error, "Error: Could not establish connection. Receiving end does not
* exist.", due to a race-condition. The disconnect event may be called by runtime.connect which
* may cause issues. We suspect that this is a chromium bug as this event should only be called
* once the port and connections are ready. Delay time is arbitrary.
*/
if (err) {
console.warn(`${err} Resetting the phishing streams.`);
setTimeout(setupPhishingExtStreams, 1000);
}
};
/**
* When the extension background is loaded it sends the EXTENSION_MESSAGES.READY message to the browser tabs.
* This listener/callback receives the message to set up the streams after service worker in-activity.
*
* @param {object} msg
* @param {string} msg.name - custom property and name to identify the message received
* @returns {Promise|undefined}
*/
const onMessageSetUpPhishingStreams = (msg) => {
if (msg.name === EXTENSION_MESSAGES.READY) {
if (!phishingExtStream) {
setupPhishingExtStreams();
}
return Promise.resolve(
`MetaMask: handled "${EXTENSION_MESSAGES.READY}" for phishing streams`,
);
}
return undefined;
};
/**
@ -177,7 +263,7 @@ const initPhishingStreams = () => {
setupPhishingPageStreams();
setupPhishingExtStreams();
phishingExtPort.onDisconnect.addListener(resetPhishingStreamAndListeners);
browser.runtime.onMessage.addListener(onMessageSetUpPhishingStreams);
};
/**
@ -191,6 +277,14 @@ const setupPageStreams = () => {
target: INPAGE,
});
if (isManifestV3) {
pageStream.on('data', ({ data: { method } }) => {
if (!IGNORE_INIT_METHODS_FOR_KEEP_ALIVE.includes(method)) {
runWorkerKeepAliveInterval();
}
});
}
// create and connect channel muxers
// so we can handle the channels individually
pageMux = new ObjectMultiplex();
@ -231,7 +325,8 @@ const setupExtensionStreams = () => {
extensionPhishingStream = extensionMux.createStream('phishing');
extensionPhishingStream.once('data', redirectToPhishingWarning);
notifyInpageOfExtensionStreamConnect();
// eslint-disable-next-line no-use-before-define
extensionPort.onDisconnect.addListener(onDisconnectDestroyStreams);
};
/** Destroys all of the extension streams */
@ -243,10 +338,13 @@ const destroyExtensionStreams = () => {
extensionChannel.removeAllListeners();
extensionChannel.destroy();
extensionStream = null;
};
/**
* LEGACY STREAM LOGIC
* TODO:LegacyProvider: Delete
*/
// TODO:LegacyProvider: Delete
@ -256,6 +354,14 @@ const setupLegacyPageStreams = () => {
target: LEGACY_INPAGE,
});
if (isManifestV3) {
legacyPageStream.on('data', ({ data: { method } }) => {
if (!IGNORE_INIT_METHODS_FOR_KEEP_ALIVE.includes(method)) {
runWorkerKeepAliveInterval();
}
});
}
legacyPageMux = new ObjectMultiplex();
legacyPageMux.setMaxListeners(25);
@ -331,19 +437,47 @@ const destroyLegacyExtensionStreams = () => {
};
/**
* Resets the extension stream with new streams to channel with the in page streams,
* and creates a new event listener to the reestablished extension port.
* When the extension background is loaded it sends the EXTENSION_MESSAGES.READY message to the browser tabs.
* This listener/callback receives the message to set up the streams after service worker in-activity.
*
* @param {object} msg
* @param {string} msg.name - custom property and name to identify the message received
* @returns {Promise|undefined}
*/
const resetStreamAndListeners = () => {
extensionPort.onDisconnect.removeListener(resetStreamAndListeners);
const onMessageSetUpExtensionStreams = (msg) => {
if (msg.name === EXTENSION_MESSAGES.READY) {
if (!extensionStream) {
setupExtensionStreams();
setupLegacyExtensionStreams();
}
return Promise.resolve(`MetaMask: handled ${EXTENSION_MESSAGES.READY}`);
}
return undefined;
};
destroyExtensionStreams();
setupExtensionStreams();
/**
* This listener destroys the extension streams when the extension port is disconnected,
* so that streams may be re-established later when the extension port is reconnected.
*/
const onDisconnectDestroyStreams = () => {
const err = checkForLastError();
extensionPort.onDisconnect.removeListener(onDisconnectDestroyStreams);
destroyExtensionStreams();
destroyLegacyExtensionStreams();
setupLegacyExtensionStreams();
extensionPort.onDisconnect.addListener(resetStreamAndListeners);
/**
* If an error is found, reset the streams. When running two or more dapps, resetting the service
* worker may cause the error, "Error: Could not establish connection. Receiving end does not
* exist.", due to a race-condition. The disconnect event may be called by runtime.connect which
* may cause issues. We suspect that this is a chromium bug as this event should only be called
* once the port and connections are ready. Delay time is arbitrary.
*/
if (err) {
console.warn(`${err} Resetting the streams.`);
setTimeout(setupExtensionStreams, 1000);
}
};
/**
@ -353,13 +487,12 @@ const resetStreamAndListeners = () => {
*/
const initStreams = () => {
setupPageStreams();
setupExtensionStreams();
// TODO:LegacyProvider: Delete
setupLegacyPageStreams();
setupExtensionStreams();
setupLegacyExtensionStreams();
extensionPort.onDisconnect.addListener(resetStreamAndListeners);
browser.runtime.onMessage.addListener(onMessageSetUpExtensionStreams);
};
// TODO:LegacyProvider: Delete
@ -389,26 +522,6 @@ function logStreamDisconnectWarning(remoteLabel, error) {
);
}
/**
* The function send message to inpage to notify it of extension stream connection
*/
function notifyInpageOfExtensionStreamConnect() {
window.postMessage(
{
target: INPAGE, // the post-message-stream "target"
data: {
// this object gets passed to obj-multiplex
name: PROVIDER, // the obj-multiplex channel name
data: {
jsonrpc: '2.0',
method: 'METAMASK_EXTENSION_STREAM_CONNECT',
},
},
},
window.location.origin,
);
}
/**
* This function must ONLY be called in pump destruction/close callbacks.
* Notifies the inpage context that streams have failed, via window.postMessage.
@ -446,12 +559,6 @@ function redirectToPhishingWarning(data = {}) {
window.location.href = `${baseUrl}#${querystring}`;
}
const initKeepWorkerAlive = () => {
setInterval(() => {
browser.runtime.sendMessage({ name: WORKER_KEEP_ALIVE_MESSAGE });
}, WORKER_KEEP_ALIVE_INTERVAL);
};
const start = () => {
const isDetectedPhishingSite =
window.location.origin === phishingPageUrl.origin &&
@ -463,9 +570,7 @@ const start = () => {
}
if (shouldInjectProvider()) {
if (isManifestV3) {
initKeepWorkerAlive();
} else {
if (!isManifestV3) {
injectScript(inpageBundle);
}
initStreams();

@ -193,11 +193,9 @@ export default class AppStateController extends EventEmitter {
const { timeoutMinutes } = this.store.getState();
if (this.timer) {
if (isManifestV3) {
chrome.alarms.clear(AUTO_LOCK_TIMEOUT_ALARM);
} else {
clearTimeout(this.timer);
}
clearTimeout(this.timer);
} else if (isManifestV3) {
chrome.alarms.clear(AUTO_LOCK_TIMEOUT_ALARM);
}
if (!timeoutMinutes) {
@ -209,16 +207,11 @@ export default class AppStateController extends EventEmitter {
delayInMinutes: timeoutMinutes,
periodInMinutes: timeoutMinutes,
});
chrome.alarms.onAlarm.addListener(() => {
chrome.alarms.getAll((alarms) => {
const hasAlarm = alarms.find(
(alarm) => alarm.name === AUTO_LOCK_TIMEOUT_ALARM,
);
if (hasAlarm) {
this.onInactiveTimeout();
chrome.alarms.clear(AUTO_LOCK_TIMEOUT_ALARM);
}
});
chrome.alarms.onAlarm.addListener((alarmInfo) => {
if (alarmInfo.name === AUTO_LOCK_TIMEOUT_ALARM) {
this.onInactiveTimeout();
chrome.alarms.clear(AUTO_LOCK_TIMEOUT_ALARM);
}
});
} else {
this.timer = setTimeout(

@ -27,6 +27,11 @@ export default class EnsController {
}
this.store = new ObservableStore(initState);
this.resetState = () => {
this.store.updateState(initState);
};
onNetworkDidChange(() => {
this.store.putState(initState);
const chainId = getCurrentChainId();

@ -20,7 +20,7 @@ import {
import { SECOND } from '../../../shared/constants/time';
import { isManifestV3 } from '../../../shared/modules/mv3.utils';
import { METAMETRICS_FINALIZE_EVENT_FRAGMENT_ALARM } from '../../../shared/constants/alarms';
import { checkAlarmExists } from '../lib/util';
import { checkAlarmExists, generateRandomId, isValidDate } from '../lib/util';
const EXTENSION_UNINSTALL_URL = 'https://metamask.io/uninstalled';
@ -32,6 +32,22 @@ const defaultCaptureException = (err) => {
});
};
// The function is used to build a unique messageId for segment messages
// It uses actionId and uniqueIdentifier from event if present
const buildUniqueMessageId = (args) => {
let messageId = '';
if (args.uniqueIdentifier) {
messageId += `${args.uniqueIdentifier}-`;
}
if (args.actionId) {
messageId += args.actionId;
}
if (messageId.length) {
return messageId;
}
return generateRandomId();
};
const exceptionsToFilter = {
[`You must pass either an "anonymousId" or a "userId".`]: true,
};
@ -60,6 +76,8 @@ const exceptionsToFilter = {
* @property {Array} [eventsBeforeMetricsOptIn] - Array of queued events added before
* a user opts into metrics.
* @property {object} [traits] - Traits that are not derived from other state keys.
* @property {Record<string any>} [previousUserTraits] - The user traits the last
* time they were computed.
*/
export default class MetaMetricsController {
@ -110,6 +128,7 @@ export default class MetaMetricsController {
this.environment = environment;
const abandonedFragments = omitBy(initState?.fragments, 'persist');
const segmentApiCalls = initState?.segmentApiCalls || {};
this.store = new ObservableStore({
participateInMetaMetrics: null,
@ -120,6 +139,9 @@ export default class MetaMetricsController {
fragments: {
...initState?.fragments,
},
segmentApiCalls: {
...segmentApiCalls,
},
});
preferencesStore.subscribe(({ currentLocale }) => {
@ -142,6 +164,15 @@ export default class MetaMetricsController {
this.finalizeEventFragment(fragment.id, { abandoned: true });
});
// Code below submits any pending segmentApiCalls to Segment if/when the controller is re-instantiated
if (isManifestV3) {
Object.values(segmentApiCalls).forEach(
({ eventType, payload, callback }) => {
this._submitSegmentAPICall(eventType, payload, callback);
},
);
}
// Close out event fragments that were created but not progressed. An
// interval is used to routinely check if a fragment has not been updated
// within the fragment's timeout window. When creating a new event fragment
@ -162,17 +193,10 @@ export default class MetaMetricsController {
});
}
});
chrome.alarms.onAlarm.addListener(() => {
chrome.alarms.getAll((alarms) => {
const hasAlarm = checkAlarmExists(
alarms,
METAMETRICS_FINALIZE_EVENT_FRAGMENT_ALARM,
);
if (hasAlarm) {
this.finalizeAbandonedFragments();
}
});
chrome.alarms.onAlarm.addListener((alarmInfo) => {
if (alarmInfo.name === METAMETRICS_FINALIZE_EVENT_FRAGMENT_ALARM) {
this.finalizeAbandonedFragments();
}
});
} else {
setInterval(() => {
@ -225,14 +249,6 @@ export default class MetaMetricsController {
);
}
const existingFragment = this.getExistingEventFragment(
options.actionId,
options.uniqueIdentifier,
);
if (existingFragment) {
return existingFragment;
}
const { fragments } = this.store.getState();
const id = options.uniqueIdentifier ?? uuidv4();
@ -260,6 +276,8 @@ export default class MetaMetricsController {
value: fragment.value,
currency: fragment.currency,
environmentType: fragment.environmentType,
actionId: options.actionId,
uniqueIdentifier: options.uniqueIdentifier,
});
}
@ -281,26 +299,6 @@ export default class MetaMetricsController {
return fragment;
}
/**
* Returns the fragment stored in memory with provided id or undefined if it
* does not exist.
*
* @param {string} actionId - actionId passed from UI
* @param {string} uniqueIdentifier - uniqueIdentifier of the event
* @returns {[MetaMetricsEventFragment]}
*/
getExistingEventFragment(actionId, uniqueIdentifier) {
const { fragments } = this.store.getState();
const existingFragment = Object.values(fragments).find(
(fragment) =>
fragment.actionId === actionId &&
fragment.uniqueIdentifier === uniqueIdentifier,
);
return existingFragment;
}
/**
* Updates an event fragment in state
*
@ -361,6 +359,8 @@ export default class MetaMetricsController {
value: fragment.value,
currency: fragment.currency,
environmentType: fragment.environmentType,
actionId: fragment.actionId,
uniqueIdentifier: fragment.uniqueIdentifier,
});
const { fragments } = this.store.getState();
delete fragments[id];
@ -447,7 +447,10 @@ export default class MetaMetricsController {
* @param {MetaMetricsPageOptions} [options] - options for handling the page
* view
*/
trackPage({ name, params, environmentType, page, referrer }, options) {
trackPage(
{ name, params, environmentType, page, referrer, actionId },
options,
) {
try {
if (this.state.participateInMetaMetrics === false) {
return;
@ -462,7 +465,8 @@ export default class MetaMetricsController {
const { metaMetricsId } = this.state;
const idTrait = metaMetricsId ? 'userId' : 'anonymousId';
const idValue = metaMetricsId ?? METAMETRICS_ANONYMOUS_ID;
this.segment.page({
this._submitSegmentAPICall('page', {
messageId: buildUniqueMessageId({ actionId }),
[idTrait]: idValue,
name,
properties: {
@ -653,6 +657,7 @@ export default class MetaMetricsController {
} = rawPayload;
return {
event,
messageId: buildUniqueMessageId(rawPayload),
properties: {
// These values are omitted from properties because they have special meaning
// in segment. https://segment.com/docs/connections/spec/track/#properties.
@ -682,7 +687,7 @@ export default class MetaMetricsController {
* @returns {MetaMetricsTraits | null} traits that have changed since last update
*/
_buildUserTraitsObject(metamaskState) {
const { traits } = this.store.getState();
const { traits, previousUserTraits } = this.store.getState();
/** @type {MetaMetricsTraits} */
const currentTraits = {
[TRAITS.ADDRESS_BOOK_ENTRIES]: sum(
@ -703,15 +708,14 @@ export default class MetaMetricsController {
},
[],
),
[TRAITS.NFT_AUTODETECTION_ENABLED]: metamaskState.useCollectibleDetection,
[TRAITS.NFT_AUTODETECTION_ENABLED]: metamaskState.useNftDetection,
[TRAITS.NUMBER_OF_ACCOUNTS]: Object.values(metamaskState.identities)
.length,
[TRAITS.NUMBER_OF_NFT_COLLECTIONS]: this._getAllUniqueNFTAddressesLength(
metamaskState.allCollectibles,
metamaskState.allNfts,
),
[TRAITS.NUMBER_OF_NFTS]: this._getAllNFTsFlattened(
metamaskState.allCollectibles,
).length,
[TRAITS.NUMBER_OF_NFTS]: this._getAllNFTsFlattened(metamaskState.allNfts)
.length,
[TRAITS.NUMBER_OF_TOKENS]: this._getNumberOfTokens(metamaskState),
[TRAITS.OPENSEA_API_ENABLED]: metamaskState.openSeaEnabled,
[TRAITS.THREE_BOX_ENABLED]: false, // deprecated, hard-coded as false
@ -719,17 +723,17 @@ export default class MetaMetricsController {
[TRAITS.TOKEN_DETECTION_ENABLED]: metamaskState.useTokenDetection,
};
if (!this.previousTraits) {
this.previousTraits = currentTraits;
if (!previousUserTraits) {
this.store.updateState({ previousUserTraits: currentTraits });
return currentTraits;
}
if (this.previousTraits && !isEqual(this.previousTraits, currentTraits)) {
if (previousUserTraits && !isEqual(previousUserTraits, currentTraits)) {
const updates = pickBy(
currentTraits,
(v, k) => !isEqual(this.previousTraits[k], v),
(v, k) => !isEqual(previousUserTraits[k], v),
);
this.previousTraits = currentTraits;
this.store.updateState({ previousUserTraits: currentTraits });
return updates;
}
@ -762,11 +766,11 @@ export default class MetaMetricsController {
* Returns an array of all of the collectibles/NFTs the user
* possesses across all networks and accounts.
*
* @param {object} allCollectibles
* @param {object} allNfts
* @returns {[]}
*/
_getAllNFTsFlattened = memoize((allCollectibles = {}) => {
return Object.values(allCollectibles).reduce((result, chainNFTs) => {
_getAllNFTsFlattened = memoize((allNfts = {}) => {
return Object.values(allNfts).reduce((result, chainNFTs) => {
return result.concat(...Object.values(chainNFTs));
}, []);
});
@ -775,11 +779,11 @@ export default class MetaMetricsController {
* Returns the number of unique collectible/NFT addresses the user
* possesses across all networks and accounts.
*
* @param {object} allCollectibles
* @param {object} allNfts
* @returns {number}
*/
_getAllUniqueNFTAddressesLength(allCollectibles = {}) {
const allNFTAddresses = this._getAllNFTsFlattened(allCollectibles).map(
_getAllUniqueNFTAddressesLength(allNfts = {}) {
const allNFTAddresses = this._getAllNFTsFlattened(allNfts).map(
(nft) => nft.address,
);
const uniqueAddresses = new Set(allNFTAddresses);
@ -815,7 +819,7 @@ export default class MetaMetricsController {
}
try {
this.segment.identify({
this._submitSegmentAPICall('identify', {
userId: metaMetricsId,
traits: userTraits,
});
@ -944,10 +948,49 @@ export default class MetaMetricsController {
return resolve();
};
this.segment.track(payload, callback);
this._submitSegmentAPICall('track', payload, callback);
if (flushImmediately) {
this.segment.flush();
}
});
}
// Method below submits the request to analytics SDK.
// It will also add event to controller store
// and pass a callback to remove it from store once request is submitted to segment
// Saving segmentApiCalls in controller store in MV3 ensures that events are tracked
// even if service worker terminates before events are submiteed to segment.
_submitSegmentAPICall(eventType, payload, callback) {
const messageId = payload.messageId || generateRandomId();
let timestamp = new Date();
if (payload.timestamp) {
const payloadDate = new Date(payload.timestamp);
if (isValidDate(payloadDate)) {
timestamp = payloadDate;
}
}
const modifiedPayload = { ...payload, messageId, timestamp };
this.store.updateState({
segmentApiCalls: {
...this.store.getState().segmentApiCalls,
[messageId]: {
eventType,
payload: {
...modifiedPayload,
timestamp: modifiedPayload.timestamp.toString(),
},
callback,
},
},
});
const modifiedCallback = (result) => {
const { segmentApiCalls } = this.store.getState();
delete segmentApiCalls[messageId];
this.store.updateState({
segmentApiCalls,
});
return callback?.(result);
};
this.segment[eventType](modifiedPayload, modifiedCallback);
}
}

@ -9,6 +9,7 @@ import {
} from '../../../shared/constants/metametrics';
import waitUntilCalled from '../../../test/lib/wait-until-called';
import { CHAIN_IDS, CURRENCY_SYMBOLS } from '../../../shared/constants/network';
import * as Utils from '../lib/util';
import MetaMetricsController from './metametrics';
import { NETWORK_EVENTS } from './network';
@ -19,6 +20,7 @@ const NETWORK = 'Mainnet';
const FAKE_CHAIN_ID = '0x1338';
const LOCALE = 'en_US';
const TEST_META_METRICS_ID = '0xabc';
const DUMMY_ACTION_ID = 'DUMMY_ACTION_ID';
const MOCK_TRAITS = {
test_boolean: true,
@ -124,9 +126,10 @@ function getMetaMetricsController({
metaMetricsId = TEST_META_METRICS_ID,
preferencesStore = getMockPreferencesStore(),
networkController = getMockNetworkController(),
segmentInstance,
} = {}) {
return new MetaMetricsController({
segment,
segment: segmentInstance || segment,
getNetworkIdentifier:
networkController.getNetworkIdentifier.bind(networkController),
getCurrentChainId:
@ -145,10 +148,17 @@ function getMetaMetricsController({
testid: SAMPLE_PERSISTED_EVENT,
testid2: SAMPLE_NON_PERSISTED_EVENT,
},
events: {},
},
});
}
describe('MetaMetricsController', function () {
const now = new Date();
let clock;
beforeEach(function () {
clock = sinon.useFakeTimers(now.getTime());
sinon.stub(Utils, 'generateRandomId').returns('DUMMY_RANDOM_ID');
});
describe('constructor', function () {
it('should properly initialize', function () {
const mock = sinon.mock(segment);
@ -163,6 +173,8 @@ describe('MetaMetricsController', function () {
...DEFAULT_EVENT_PROPERTIES,
test: true,
},
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});
const metaMetricsController = getMetaMetricsController();
assert.strictEqual(metaMetricsController.version, VERSION);
@ -233,15 +245,18 @@ describe('MetaMetricsController', function () {
});
const mock = sinon.mock(segment);
mock
.expects('identify')
.once()
.withArgs({ userId: TEST_META_METRICS_ID, traits: MOCK_TRAITS });
mock.expects('identify').once().withArgs({
userId: TEST_META_METRICS_ID,
traits: MOCK_TRAITS,
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});
metaMetricsController.identify({
...MOCK_TRAITS,
...MOCK_INVALID_TRAITS,
});
mock.verify();
});
@ -263,6 +278,8 @@ describe('MetaMetricsController', function () {
traits: {
test_date: mockDateISOString,
},
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});
metaMetricsController.identify({
@ -358,6 +375,8 @@ describe('MetaMetricsController', function () {
test: 1,
...DEFAULT_EVENT_PROPERTIES,
},
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});
metaMetricsController.submitEvent(
{
@ -388,6 +407,8 @@ describe('MetaMetricsController', function () {
test: 1,
...DEFAULT_EVENT_PROPERTIES,
},
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});
metaMetricsController.submitEvent(
{
@ -417,6 +438,8 @@ describe('MetaMetricsController', function () {
legacy_event: true,
...DEFAULT_EVENT_PROPERTIES,
},
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});
metaMetricsController.submitEvent(
{
@ -439,12 +462,14 @@ describe('MetaMetricsController', function () {
.once()
.withArgs({
event: 'Fake Event',
userId: TEST_META_METRICS_ID,
context: DEFAULT_TEST_CONTEXT,
properties: {
test: 1,
...DEFAULT_EVENT_PROPERTIES,
},
context: DEFAULT_TEST_CONTEXT,
userId: TEST_META_METRICS_ID,
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});
metaMetricsController.submitEvent({
event: 'Fake Event',
@ -519,6 +544,8 @@ describe('MetaMetricsController', function () {
foo: 'bar',
...DEFAULT_EVENT_PROPERTIES,
},
messageId: Utils.generateRandomId(),
timestamp: new Date(),
}),
);
assert.ok(
@ -527,6 +554,8 @@ describe('MetaMetricsController', function () {
userId: TEST_META_METRICS_ID,
context: DEFAULT_TEST_CONTEXT,
properties: DEFAULT_EVENT_PROPERTIES,
messageId: Utils.generateRandomId(),
timestamp: new Date(),
}),
);
});
@ -547,6 +576,8 @@ describe('MetaMetricsController', function () {
params: null,
...DEFAULT_PAGE_PROPERTIES,
},
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});
metaMetricsController.trackPage({
name: 'home',
@ -590,6 +621,8 @@ describe('MetaMetricsController', function () {
params: null,
...DEFAULT_PAGE_PROPERTIES,
},
messageId: Utils.generateRandomId(),
timestamp: new Date(),
});
metaMetricsController.trackPage(
{
@ -602,6 +635,50 @@ describe('MetaMetricsController', function () {
);
mock.verify();
});
it('multiple trackPage call with same actionId should result in same messageId being sent to segment', function () {
const mock = sinon.mock(segment);
const metaMetricsController = getMetaMetricsController({
preferencesStore: getMockPreferencesStore({
participateInMetaMetrics: null,
}),
});
mock
.expects('page')
.twice()
.withArgs({
name: 'home',
userId: TEST_META_METRICS_ID,
context: DEFAULT_TEST_CONTEXT,
properties: {
params: null,
...DEFAULT_PAGE_PROPERTIES,
},
messageId: DUMMY_ACTION_ID,
timestamp: new Date(),
});
metaMetricsController.trackPage(
{
name: 'home',
params: null,
actionId: DUMMY_ACTION_ID,
environmentType: ENVIRONMENT_TYPE_BACKGROUND,
page: METAMETRICS_BACKGROUND_PAGE_OBJECT,
},
{ isOptInPath: true },
);
metaMetricsController.trackPage(
{
name: 'home',
params: null,
actionId: DUMMY_ACTION_ID,
environmentType: ENVIRONMENT_TYPE_BACKGROUND,
page: METAMETRICS_BACKGROUND_PAGE_OBJECT,
},
{ isOptInPath: true },
);
mock.verify();
});
});
describe('_buildUserTraitsObject', function () {
@ -640,7 +717,7 @@ describe('MetaMetricsController', function () {
[CHAIN_IDS.MAINNET]: [{ address: '0x' }],
[CHAIN_IDS.GOERLI]: [{ address: '0x' }, { address: '0x0' }],
},
allCollectibles: {
allNfts: {
'0xac706cE8A9BF27Afecf080fB298d0ee13cfb978A': {
56: [
{
@ -675,7 +752,7 @@ describe('MetaMetricsController', function () {
identities: [{}, {}],
ledgerTransportType: 'web-hid',
openSeaEnabled: true,
useCollectibleDetection: false,
useNftDetection: false,
theme: 'default',
useTokenDetection: true,
});
@ -713,7 +790,7 @@ describe('MetaMetricsController', function () {
ledgerTransportType: 'web-hid',
openSeaEnabled: true,
identities: [{}, {}],
useCollectibleDetection: false,
useNftDetection: false,
theme: 'default',
useTokenDetection: true,
});
@ -733,7 +810,7 @@ describe('MetaMetricsController', function () {
ledgerTransportType: 'web-hid',
openSeaEnabled: false,
identities: [{}, {}, {}],
useCollectibleDetection: false,
useNftDetection: false,
theme: 'default',
useTokenDetection: true,
});
@ -761,7 +838,7 @@ describe('MetaMetricsController', function () {
ledgerTransportType: 'web-hid',
openSeaEnabled: true,
identities: [{}, {}],
useCollectibleDetection: true,
useNftDetection: true,
theme: 'default',
useTokenDetection: true,
});
@ -779,7 +856,7 @@ describe('MetaMetricsController', function () {
ledgerTransportType: 'web-hid',
openSeaEnabled: true,
identities: [{}, {}],
useCollectibleDetection: true,
useNftDetection: true,
theme: 'default',
useTokenDetection: true,
});
@ -788,9 +865,35 @@ describe('MetaMetricsController', function () {
});
});
describe('submitting segmentApiCalls to segment SDK', function () {
it('should add event to store when submitting to SDK', function () {
const metaMetricsController = getMetaMetricsController({});
metaMetricsController.trackPage({}, { isOptIn: true });
const { segmentApiCalls } = metaMetricsController.store.getState();
assert(Object.keys(segmentApiCalls).length > 0);
});
it('should remove event from store when callback is invoked', function () {
const segmentInstance = createSegmentMock(2, 10000);
const stubFn = (_, cb) => {
cb();
};
sinon.stub(segmentInstance, 'track').callsFake(stubFn);
sinon.stub(segmentInstance, 'page').callsFake(stubFn);
const metaMetricsController = getMetaMetricsController({
segmentInstance,
});
metaMetricsController.trackPage({}, { isOptIn: true });
const { segmentApiCalls } = metaMetricsController.store.getState();
assert(Object.keys(segmentApiCalls).length === 0);
});
});
afterEach(function () {
// flush the queues manually after each test
segment.flush();
clock.restore();
sinon.restore();
});
});

@ -1,4 +1,4 @@
import { endowmentPermissionBuilders } from '@metamask/snap-controllers';
import { endowmentPermissionBuilders } from '@metamask/snaps-controllers';
import {
restrictedMethodPermissionBuilders,
selectHooks,

@ -1,6 +1,6 @@
import { constructPermission, PermissionType } from '@metamask/controllers';
///: BEGIN:ONLY_INCLUDE_IN(flask)
import { endowmentCaveatSpecifications as snapsEndowmentCaveatSpecifications } from '@metamask/snap-controllers';
import { endowmentCaveatSpecifications as snapsEndowmentCaveatSpecifications } from '@metamask/snaps-controllers';
import { caveatSpecifications as snapsCaveatsSpecifications } from '@metamask/rpc-methods';
///: END:ONLY_INCLUDE_IN
import {
@ -11,7 +11,7 @@ import {
/**
* This file contains the specifications of the permissions and caveats
* that are recognized by our permission system. See the PermissionController
* README in @metamask/snap-controllers for details.
* README in @metamask/controllers for details.
*/
/**

@ -16,7 +16,7 @@ describe('PermissionController specifications', () => {
describe('caveat specifications', () => {
it('getCaveatSpecifications returns the expected specifications object', () => {
const caveatSpecifications = getCaveatSpecifications({});
expect(Object.keys(caveatSpecifications)).toHaveLength(4);
expect(Object.keys(caveatSpecifications)).toHaveLength(6);
expect(
caveatSpecifications[CaveatTypes.restrictReturnedAccounts].type,
).toStrictEqual(CaveatTypes.restrictReturnedAccounts);
@ -30,6 +30,12 @@ describe('PermissionController specifications', () => {
expect(caveatSpecifications.snapKeyring.type).toStrictEqual(
SnapCaveatType.SnapKeyring,
);
expect(caveatSpecifications.snapCronjob.type).toStrictEqual(
SnapCaveatType.SnapCronjob,
);
expect(caveatSpecifications.transactionOrigin.type).toStrictEqual(
SnapCaveatType.TransactionOrigin,
);
});
describe('restrictReturnedAccounts', () => {

@ -39,7 +39,7 @@ export default class PreferencesController {
// set to true means the dynamic list from the API is being used
// set to false will be using the static list from contract-metadata
useTokenDetection: false,
useCollectibleDetection: false,
useNftDetection: false,
openSeaEnabled: false,
advancedGasFee: null,
@ -69,6 +69,8 @@ export default class PreferencesController {
? LEDGER_TRANSPORT_TYPES.WEBHID
: LEDGER_TRANSPORT_TYPES.U2F,
theme: 'light',
improvedTokenAllowanceEnabled: false,
transactionSecurityCheckEnabled: false,
...opts.initState,
};
@ -141,12 +143,12 @@ export default class PreferencesController {
}
/**
* Setter for the `useCollectibleDetection` property
* Setter for the `useNftDetection` property
*
* @param {boolean} useCollectibleDetection - Whether or not the user prefers to autodetect collectibles.
* @param {boolean} useNftDetection - Whether or not the user prefers to autodetect collectibles.
*/
setUseCollectibleDetection(useCollectibleDetection) {
this.store.updateState({ useCollectibleDetection });
setUseNftDetection(useNftDetection) {
this.store.updateState({ useNftDetection });
}
/**
@ -187,6 +189,28 @@ export default class PreferencesController {
this.store.updateState({ theme: val });
}
/**
* Setter for the `improvedTokenAllowanceEnabled` property
*
* @param improvedTokenAllowanceEnabled
*/
setImprovedTokenAllowanceEnabled(improvedTokenAllowanceEnabled) {
this.store.updateState({
improvedTokenAllowanceEnabled,
});
}
/**
* Setter for the `transactionSecurityCheckEnabled` property
*
* @param transactionSecurityCheckEnabled
*/
setTransactionSecurityCheckEnabled(transactionSecurityCheckEnabled) {
this.store.updateState({
transactionSecurityCheckEnabled,
});
}
/**
* Add new methodData to state, to avoid requesting this information again through Infura
*

@ -308,21 +308,21 @@ describe('preferences controller', function () {
});
});
describe('setUseCollectibleDetection', function () {
describe('setUseNftDetection', function () {
it('should default to false', function () {
const state = preferencesController.store.getState();
assert.equal(state.useCollectibleDetection, false);
assert.equal(state.useNftDetection, false);
});
it('should set the useCollectibleDetection property in state', function () {
it('should set the useNftDetection property in state', function () {
assert.equal(
preferencesController.store.getState().useCollectibleDetection,
preferencesController.store.getState().useNftDetection,
false,
);
preferencesController.setOpenSeaEnabled(true);
preferencesController.setUseCollectibleDetection(true);
preferencesController.setUseNftDetection(true);
assert.equal(
preferencesController.store.getState().useCollectibleDetection,
preferencesController.store.getState().useNftDetection,
true,
);
});

@ -115,6 +115,10 @@ export default class SwapsController {
swapsState: { ...initialState.swapsState },
});
this.resetState = () => {
this.store.updateState({ swapsState: { ...initialState.swapsState } });
};
this._fetchTradesInfo = fetchTradesInfo;
this._getCurrentChainId = getCurrentChainId;
this._getEIP1559GasFeeEstimates = getEIP1559GasFeeEstimates;

@ -151,6 +151,11 @@ export default class TransactionController extends EventEmitter {
this.getTokenStandardAndDetails = opts.getTokenStandardAndDetails;
this.memStore = new ObservableStore({});
this.resetState = () => {
this._updateMemstore();
};
this.query = new EthQuery(this.provider);
this.txGasUtil = new TxGasUtil(this.provider);

@ -14,7 +14,10 @@ import log from 'loglevel';
import pify from 'pify';
import { ethers } from 'ethers';
import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi';
import { CHAIN_IDS } from '../../../shared/constants/network';
import {
CHAIN_IDS,
LOCALHOST_RPC_URL,
} from '../../../shared/constants/network';
import {
SINGLE_CALL_BALANCES_ADDRESS,
@ -50,6 +53,7 @@ export default class AccountTracker {
* @param {object} opts.provider - An EIP-1193 provider instance that uses the current global network
* @param {object} opts.blockTracker - A block tracker, which emits events for each new block
* @param {Function} opts.getCurrentChainId - A function that returns the `chainId` for the current global network
* @param {Function} opts.getNetworkIdentifier - A function that returns the current network
*/
constructor(opts = {}) {
const initState = {
@ -58,6 +62,10 @@ export default class AccountTracker {
};
this.store = new ObservableStore(initState);
this.resetState = () => {
this.store.updateState(initState);
};
this._provider = opts.provider;
this._query = pify(new EthQuery(this._provider));
this._blockTracker = opts.blockTracker;
@ -69,6 +77,7 @@ export default class AccountTracker {
// bind function for easier listener syntax
this._updateForBlock = this._updateForBlock.bind(this);
this.getCurrentChainId = opts.getCurrentChainId;
this.getNetworkIdentifier = opts.getNetworkIdentifier;
this.ethersProvider = new ethers.providers.Web3Provider(this._provider);
}
@ -199,73 +208,79 @@ export default class AccountTracker {
const { accounts } = this.store.getState();
const addresses = Object.keys(accounts);
const chainId = this.getCurrentChainId();
switch (chainId) {
case CHAIN_IDS.MAINNET:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS,
);
break;
case CHAIN_IDS.GOERLI:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS_GOERLI,
);
break;
case CHAIN_IDS.SEPOLIA:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS_SEPOLIA,
);
break;
case CHAIN_IDS.BSC:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS_BSC,
);
break;
case CHAIN_IDS.OPTIMISM:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS_OPTIMISM,
);
break;
case CHAIN_IDS.POLYGON:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS_POLYGON,
);
break;
case CHAIN_IDS.AVALANCHE:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS_AVALANCHE,
);
break;
case CHAIN_IDS.FANTOM:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS_FANTOM,
);
break;
case CHAIN_IDS.ARBITRUM:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS_ARBITRUM,
);
break;
default:
await Promise.all(addresses.map(this._updateAccount.bind(this)));
const networkId = this.getNetworkIdentifier();
const rpcUrl = 'http://127.0.0.1:8545';
if (networkId === LOCALHOST_RPC_URL || networkId === rpcUrl) {
await Promise.all(addresses.map(this._updateAccount.bind(this)));
} else {
switch (chainId) {
case CHAIN_IDS.MAINNET:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS,
);
break;
case CHAIN_IDS.GOERLI:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS_GOERLI,
);
break;
case CHAIN_IDS.SEPOLIA:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS_SEPOLIA,
);
break;
case CHAIN_IDS.BSC:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS_BSC,
);
break;
case CHAIN_IDS.OPTIMISM:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS_OPTIMISM,
);
break;
case CHAIN_IDS.POLYGON:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS_POLYGON,
);
break;
case CHAIN_IDS.AVALANCHE:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS_AVALANCHE,
);
break;
case CHAIN_IDS.FANTOM:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS_FANTOM,
);
break;
case CHAIN_IDS.ARBITRUM:
await this._updateAccountsViaBalanceChecker(
addresses,
SINGLE_CALL_BALANCES_ADDRESS_ARBITRUM,
);
break;
default:
await Promise.all(addresses.map(this._updateAccount.bind(this)));
}
}
}

@ -41,6 +41,14 @@ export default class DecryptMessageManager extends EventEmitter {
unapprovedDecryptMsgs: {},
unapprovedDecryptMsgCount: 0,
});
this.resetState = () => {
this.memStore.updateState({
unapprovedDecryptMsgs: {},
unapprovedDecryptMsgCount: 0,
});
};
this.messages = [];
this.metricsEvent = opts.metricsEvent;
}

@ -36,6 +36,14 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
unapprovedEncryptionPublicKeyMsgs: {},
unapprovedEncryptionPublicKeyMsgCount: 0,
});
this.resetState = () => {
this.memStore.updateState({
unapprovedEncryptionPublicKeyMsgs: {},
unapprovedEncryptionPublicKeyMsgCount: 0,
});
};
this.messages = [];
this.metricsEvent = opts.metricsEvent;
}

@ -36,6 +36,14 @@ export default class MessageManager extends EventEmitter {
unapprovedMsgs: {},
unapprovedMsgCount: 0,
});
this.resetState = () => {
this.memStore.updateState({
unapprovedMsgs: {},
unapprovedMsgCount: 0,
});
};
this.messages = [];
this.metricsEvent = metricsEvent;
}

@ -43,6 +43,14 @@ export default class PersonalMessageManager extends EventEmitter {
unapprovedPersonalMsgs: {},
unapprovedPersonalMsgCount: 0,
});
this.resetState = () => {
this.memStore.updateState({
unapprovedPersonalMsgs: {},
unapprovedPersonalMsgCount: 0,
});
};
this.messages = [];
this.metricsEvent = metricsEvent;
}

@ -1,6 +1,8 @@
import KeyringController from 'eth-keyring-controller';
import log from 'loglevel';
import { KEYRING_TYPES } from '../../../shared/constants/keyrings';
const seedPhraseVerifier = {
/**
* Verifies if the seed words can restore the accounts.
@ -20,7 +22,9 @@ const seedPhraseVerifier = {
}
const keyringController = new KeyringController({});
const Keyring = keyringController.getKeyringClassForType('HD Key Tree');
const Keyring = keyringController.getKeyringClassForType(
KEYRING_TYPES.HD_KEY_TREE,
);
const opts = {
mnemonic: seedPhrase,
numberOfAccounts: createdAccounts.length,

@ -6,12 +6,13 @@ import { cloneDeep } from 'lodash';
import KeyringController from 'eth-keyring-controller';
import firstTimeState from '../first-time-state';
import mockEncryptor from '../../../test/lib/mock-encryptor';
import { KEYRING_TYPES } from '../../../shared/constants/keyrings';
import seedPhraseVerifier from './seed-phrase-verifier';
describe('SeedPhraseVerifier', () => {
describe('verifyAccounts', () => {
const password = 'passw0rd1';
const hdKeyTree = 'HD Key Tree';
const hdKeyTree = KEYRING_TYPES.HD_KEY_TREE;
let keyringController;
let primaryKeyring;

@ -2,21 +2,10 @@ import removeSlash from 'remove-trailing-slash';
import looselyValidate from '@segment/loosely-validate-event';
import { isString } from 'lodash';
import isRetryAllowed from 'is-retry-allowed';
import { generateRandomId } from '../util';
const noop = () => ({});
// Taken from https://stackoverflow.com/a/1349426/3696652
const characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const generateRandomId = () => {
let result = '';
const charactersLength = characters.length;
for (let i = 0; i < 20; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
};
// Method below is inspired from axios-retry https://github.com/softonic/axios-retry
function isNetworkError(error) {
return (

@ -1,6 +1,8 @@
import ObjectMultiplex from 'obj-multiplex';
import pump from 'pump';
import { EXTENSION_MESSAGES } from '../../../shared/constants/app';
/**
* Sets up stream multiplexing for the given stream
*
@ -9,6 +11,14 @@ import pump from 'pump';
*/
export function setupMultiplex(connectionStream) {
const mux = new ObjectMultiplex();
/**
* We are using this streams to send keep alive message between backend/ui without setting up a multiplexer
* We need to tell the multiplexer to ignore them, else we get the " orphaned data for stream " warnings
* https://github.com/MetaMask/object-multiplex/blob/280385401de84f57ef57054d92cfeb8361ef2680/src/ObjectMultiplex.ts#L63
*/
mux.ignoreStream(EXTENSION_MESSAGES.CONNECTION_READY);
mux.ignoreStream('ACK_KEEP_ALIVE_MESSAGE');
mux.ignoreStream('WORKER_KEEP_ALIVE_MESSAGE');
pump(connectionStream, mux, connectionStream, (err) => {
if (err) {
console.error(err);

@ -43,6 +43,14 @@ export default class TypedMessageManager extends EventEmitter {
unapprovedTypedMessages: {},
unapprovedTypedMessagesCount: 0,
});
this.resetState = () => {
this.memStore.updateState({
unapprovedTypedMessages: {},
unapprovedTypedMessagesCount: 0,
});
};
this.messages = [];
this.metricsEvent = metricsEvent;
}

@ -96,6 +96,7 @@ function BnMultiplyByFraction(targetBN, numerator, denominator) {
* Returns an Error if extension.runtime.lastError is present
* this is a workaround for the non-standard error object that's used
*
* @deprecated use checkForLastError in shared/modules/browser-runtime.utils.js
* @returns {Error|undefined}
*/
function checkForError() {
@ -174,3 +175,19 @@ export {
getChainType,
checkAlarmExists,
};
// Taken from https://stackoverflow.com/a/1349426/3696652
const characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
export const generateRandomId = () => {
let result = '';
const charactersLength = characters.length;
for (let i = 0; i < 20; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
};
export const isValidDate = (d) => {
return d instanceof Date && !isNaN(d);
};

@ -23,6 +23,12 @@ const browserPolyfillMock = {
},
getPlatformInfo: async () => 'mac',
},
storage: {
local: {
get: sinon.stub().resolves({}),
set: sinon.stub().resolves(),
},
},
};
let loggerMiddlewareMock;

@ -34,9 +34,9 @@ import {
TokenListController,
TokensController,
TokenRatesController,
CollectiblesController,
NftController,
AssetsContractController,
CollectibleDetectionController,
NftDetectionController,
PermissionController,
SubjectMetadataController,
PermissionsRequestNotFoundError,
@ -49,9 +49,10 @@ import {
import SmartTransactionsController from '@metamask/smart-transactions-controller';
///: BEGIN:ONLY_INCLUDE_IN(flask)
import {
CronjobController,
SnapController,
IframeExecutionService,
} from '@metamask/snap-controllers';
} from '@metamask/snaps-controllers';
///: END:ONLY_INCLUDE_IN
import {
@ -65,11 +66,9 @@ import {
GAS_DEV_API_BASE_URL,
SWAPS_CLIENT_ID,
} from '../../shared/constants/swaps';
import { KEYRING_TYPES } from '../../shared/constants/keyrings';
import { CHAIN_IDS } from '../../shared/constants/network';
import {
DEVICE_NAMES,
KEYRING_TYPES,
} from '../../shared/constants/hardware-wallets';
import { DEVICE_NAMES } from '../../shared/constants/hardware-wallets';
import {
CaveatTypes,
RestrictedMethods,
@ -101,6 +100,7 @@ import {
getTokenValueParam,
hexToDecimal,
} from '../../shared/lib/metamask-controller-utils';
import { isManifestV3 } from '../../shared/modules/mv3.utils';
import {
onMessageReceived,
checkForMultipleVersionsRunning,
@ -313,7 +313,7 @@ export default class MetamaskController extends EventEmitter {
initState.AssetsContractController,
);
this.collectiblesController = new CollectiblesController(
this.nftController = new NftController(
{
onPreferencesStateChange:
this.preferencesController.store.subscribe.bind(
@ -344,14 +344,14 @@ export default class MetamaskController extends EventEmitter {
this.assetsContractController.getERC1155TokenURI.bind(
this.assetsContractController,
),
onCollectibleAdded: ({ address, symbol, tokenId, standard, source }) =>
onNftAdded: ({ address, symbol, tokenId, standard, source }) =>
this.metaMetricsController.trackEvent({
event: EVENT_NAMES.NFT_ADDED,
category: EVENT.CATEGORIES.WALLET,
properties: {
token_contract_address: address,
token_symbol: symbol,
asset_type: ASSET_TYPES.COLLECTIBLE,
asset_type: ASSET_TYPES.NFT,
token_standard: standard,
source,
},
@ -361,34 +361,29 @@ export default class MetamaskController extends EventEmitter {
}),
},
{},
initState.CollectiblesController,
initState.NftController,
);
this.collectiblesController.setApiKey(process.env.OPENSEA_KEY);
this.nftController.setApiKey(process.env.OPENSEA_KEY);
process.env.COLLECTIBLES_V1 &&
(this.collectibleDetectionController = new CollectibleDetectionController(
{
onCollectiblesStateChange: (listener) =>
this.collectiblesController.subscribe(listener),
onPreferencesStateChange:
this.preferencesController.store.subscribe.bind(
this.preferencesController.store,
),
onNetworkStateChange: this.networkController.store.subscribe.bind(
this.networkController.store,
(this.nftDetectionController = new NftDetectionController({
onNftsStateChange: (listener) => this.nftController.subscribe(listener),
onPreferencesStateChange:
this.preferencesController.store.subscribe.bind(
this.preferencesController.store,
),
getOpenSeaApiKey: () => this.collectiblesController.openSeaApiKey,
getBalancesInSingleCall:
this.assetsContractController.getBalancesInSingleCall.bind(
this.assetsContractController,
),
addCollectible: this.collectiblesController.addCollectible.bind(
this.collectiblesController,
onNetworkStateChange: this.networkController.store.subscribe.bind(
this.networkController.store,
),
getOpenSeaApiKey: () => this.nftController.openSeaApiKey,
getBalancesInSingleCall:
this.assetsContractController.getBalancesInSingleCall.bind(
this.assetsContractController,
),
getCollectiblesState: () => this.collectiblesController.state,
},
));
addNft: this.nftController.addNft.bind(this.nftController),
getNftState: () => this.nftController.state,
}));
this.metaMetricsController = new MetaMetricsController({
segment,
@ -423,6 +418,7 @@ export default class MetamaskController extends EventEmitter {
: GAS_API_BASE_URL;
this.gasFeeController = new GasFeeController({
state: initState.GasFeeController,
interval: 10000,
messenger: gasFeeMessenger,
clientId: SWAPS_CLIENT_ID,
@ -487,26 +483,30 @@ export default class MetamaskController extends EventEmitter {
);
// token exchange rate tracker
this.tokenRatesController = new TokenRatesController({
onTokensStateChange: (listener) =>
this.tokensController.subscribe(listener),
onCurrencyRateStateChange: (listener) =>
this.controllerMessenger.subscribe(
`${this.currencyRateController.name}:stateChange`,
listener,
),
onNetworkStateChange: (cb) =>
this.networkController.store.subscribe((networkState) => {
const modifiedNetworkState = {
...networkState,
provider: {
...networkState.provider,
chainId: hexToDecimal(networkState.provider.chainId),
},
};
return cb(modifiedNetworkState);
}),
});
this.tokenRatesController = new TokenRatesController(
{
onTokensStateChange: (listener) =>
this.tokensController.subscribe(listener),
onCurrencyRateStateChange: (listener) =>
this.controllerMessenger.subscribe(
`${this.currencyRateController.name}:stateChange`,
listener,
),
onNetworkStateChange: (cb) =>
this.networkController.store.subscribe((networkState) => {
const modifiedNetworkState = {
...networkState,
provider: {
...networkState.provider,
chainId: hexToDecimal(networkState.provider.chainId),
},
};
return cb(modifiedNetworkState);
}),
},
undefined,
initState.TokenRatesController,
);
this.ensController = new EnsController({
provider: this.provider,
@ -539,6 +539,9 @@ export default class MetamaskController extends EventEmitter {
getCurrentChainId: this.networkController.getCurrentChainId.bind(
this.networkController,
),
getNetworkIdentifier: this.networkController.getNetworkIdentifier.bind(
this.networkController,
),
});
// start and stop polling for balances based on activeControllerConnections
@ -664,7 +667,7 @@ export default class MetamaskController extends EventEmitter {
///: BEGIN:ONLY_INCLUDE_IN(flask)
this.snapExecutionService = new IframeExecutionService({
iframeUrl: new URL(
'https://metamask.github.io/iframe-execution-environment/0.9.1',
'https://metamask.github.io/iframe-execution-environment/0.11.0',
),
messenger: this.controllerMessenger.getRestricted({
name: 'ExecutionService',
@ -722,6 +725,7 @@ export default class MetamaskController extends EventEmitter {
});
this.rateLimitController = new RateLimitController({
state: initState.RateLimitController,
messenger: this.controllerMessenger.getRestricted({
name: 'RateLimitController',
}),
@ -750,6 +754,24 @@ export default class MetamaskController extends EventEmitter {
},
},
});
// --- Snaps Cronjob Controller configuration
const cronjobControllerMessenger = this.controllerMessenger.getRestricted({
name: 'CronjobController',
allowedEvents: [
'SnapController:snapInstalled',
'SnapController:snapUpdated',
'SnapController:snapRemoved',
],
allowedActions: [
`${this.permissionController.name}:getPermissions`,
'SnapController:handleRequest',
'SnapController:getAll',
],
});
this.cronjobController = new CronjobController({
state: initState.CronjobController,
messenger: cronjobControllerMessenger,
});
///: END:ONLY_INCLUDE_IN
this.detectTokensController = new DetectTokensController({
preferences: this.preferencesController,
@ -875,12 +897,10 @@ export default class MetamaskController extends EventEmitter {
const transactionDataTokenId =
getTokenIdParam(transactionData) ??
getTokenValueParam(transactionData);
const { allCollectibles } = this.collectiblesController.state;
const { allNfts } = this.nftController.state;
// check if its a known collectible
const knownCollectible = allCollectibles?.[userAddress]?.[
chainId
].find(
const knownCollectible = allNfts?.[userAddress]?.[chainId].find(
({ address, tokenId }) =>
isEqualCaseInsensitive(address, contractAddress) &&
tokenId === transactionDataTokenId,
@ -888,7 +908,7 @@ export default class MetamaskController extends EventEmitter {
// if it is we check and update ownership status.
if (knownCollectible) {
this.collectiblesController.checkAndUpdateSingleCollectibleOwnershipStatus(
this.nftController.checkAndUpdateSingleNftOwnershipStatus(
knownCollectible,
false,
{ userAddress, chainId },
@ -896,7 +916,7 @@ export default class MetamaskController extends EventEmitter {
}
}
const metamaskState = await this.getState();
const metamaskState = this.getState();
if (txReceipt && txReceipt.status === '0x0') {
this.metaMetricsController.trackEvent(
@ -1016,6 +1036,24 @@ export default class MetamaskController extends EventEmitter {
// ensure isClientOpenAndUnlocked is updated when memState updates
this.on('update', (memState) => this._onStateUpdate(memState));
/**
* All controllers in Memstore but not in store. They are not persisted.
* On chrome profile re-start, they will be re-initialized.
*/
const resetOnRestartStore = {
AccountTracker: this.accountTracker.store,
TxController: this.txController.memStore,
TokenRatesController: this.tokenRatesController,
MessageManager: this.messageManager.memStore,
PersonalMessageManager: this.personalMessageManager.memStore,
DecryptMessageManager: this.decryptMessageManager.memStore,
EncryptionPublicKeyManager: this.encryptionPublicKeyManager.memStore,
TypesMessageManager: this.typedMessageManager.memStore,
SwapsController: this.swapsController.store,
EnsController: this.ensController.store,
ApprovalController: this.approvalController,
};
this.store.updateStructure({
AppStateController: this.appStateController.store,
TransactionController: this.txController.store,
@ -1038,26 +1076,20 @@ export default class MetamaskController extends EventEmitter {
TokenListController: this.tokenListController,
TokensController: this.tokensController,
SmartTransactionsController: this.smartTransactionsController,
CollectiblesController: this.collectiblesController,
NftController: this.nftController,
///: BEGIN:ONLY_INCLUDE_IN(flask)
SnapController: this.snapController,
CronjobController: this.cronjobController,
NotificationController: this.notificationController,
///: END:ONLY_INCLUDE_IN
...resetOnRestartStore,
});
this.memStore = new ComposableObservableStore({
config: {
AppStateController: this.appStateController.store,
NetworkController: this.networkController.store,
AccountTracker: this.accountTracker.store,
TxController: this.txController.memStore,
CachedBalancesController: this.cachedBalancesController.store,
TokenRatesController: this.tokenRatesController,
MessageManager: this.messageManager.memStore,
PersonalMessageManager: this.personalMessageManager.memStore,
DecryptMessageManager: this.decryptMessageManager.memStore,
EncryptionPublicKeyManager: this.encryptionPublicKeyManager.memStore,
TypesMessageManager: this.typedMessageManager.memStore,
KeyringController: this.keyringController.memStore,
PreferencesController: this.preferencesController.store,
MetaMetricsController: this.metaMetricsController.store,
@ -1071,24 +1103,47 @@ export default class MetamaskController extends EventEmitter {
PermissionLogController: this.permissionLogController.store,
SubjectMetadataController: this.subjectMetadataController,
BackupController: this.backupController,
SwapsController: this.swapsController.store,
EnsController: this.ensController.store,
ApprovalController: this.approvalController,
AnnouncementController: this.announcementController,
GasFeeController: this.gasFeeController,
TokenListController: this.tokenListController,
TokensController: this.tokensController,
SmartTransactionsController: this.smartTransactionsController,
CollectiblesController: this.collectiblesController,
NftController: this.nftController,
///: BEGIN:ONLY_INCLUDE_IN(flask)
SnapController: this.snapController,
CronjobController: this.cronjobController,
NotificationController: this.notificationController,
///: END:ONLY_INCLUDE_IN
...resetOnRestartStore,
},
controllerMessenger: this.controllerMessenger,
});
this.memStore.subscribe(this.sendUpdate.bind(this));
// if this is the first time, clear the state of by calling these methods
const resetMethods = [
this.accountTracker.resetState,
this.txController.resetState,
this.messageManager.resetState,
this.personalMessageManager.resetState,
this.decryptMessageManager.resetState,
this.encryptionPublicKeyManager.resetState,
this.typedMessageManager.resetState,
this.swapsController.resetState,
this.ensController.resetState,
this.approvalController.clear.bind(this.approvalController),
// WE SHOULD ADD TokenListController.resetState here too. But it's not implemented yet.
];
if (isManifestV3) {
if (globalThis.isFirstTimeProfileLoaded === true) {
this.resetStates(resetMethods);
}
} else {
// it's always the first time in MV2
this.resetStates(resetMethods);
}
const password = process.env.CONF?.PASSWORD;
if (
password &&
@ -1122,6 +1177,18 @@ export default class MetamaskController extends EventEmitter {
checkForMultipleVersionsRunning();
}
resetStates(resetMethods) {
resetMethods.forEach((resetMethod) => {
try {
resetMethod();
} catch (err) {
console.error(err);
}
});
globalThis.isFirstTimeProfileLoaded = false;
}
///: BEGIN:ONLY_INCLUDE_IN(flask)
/**
* Constructor helper for getting Snap permission specifications.
@ -1130,10 +1197,6 @@ export default class MetamaskController extends EventEmitter {
return {
...buildSnapEndowmentSpecifications(),
...buildSnapRestrictedMethodSpecifications({
addSnap: this.controllerMessenger.call.bind(
this.controllerMessenger,
'SnapController:add',
),
clearSnapState: this.controllerMessenger.call.bind(
this.controllerMessenger,
'SnapController:clearSnapState',
@ -1486,8 +1549,8 @@ export default class MetamaskController extends EventEmitter {
addressBookController,
alertController,
appStateController,
collectiblesController,
collectibleDetectionController,
nftController,
nftDetectionController,
currencyRateController,
detectTokensController,
ensController,
@ -1526,10 +1589,9 @@ export default class MetamaskController extends EventEmitter {
setUseTokenDetection: preferencesController.setUseTokenDetection.bind(
preferencesController,
),
setUseCollectibleDetection:
preferencesController.setUseCollectibleDetection.bind(
preferencesController,
),
setUseNftDetection: preferencesController.setUseNftDetection.bind(
preferencesController,
),
setOpenSeaEnabled: preferencesController.setOpenSeaEnabled.bind(
preferencesController,
),
@ -1632,41 +1694,36 @@ export default class MetamaskController extends EventEmitter {
preferencesController,
),
setTheme: preferencesController.setTheme.bind(preferencesController),
setImprovedTokenAllowanceEnabled:
preferencesController.setImprovedTokenAllowanceEnabled.bind(
preferencesController,
),
setTransactionSecurityCheckEnabled:
preferencesController.setTransactionSecurityCheckEnabled.bind(
preferencesController,
),
// AssetsContractController
getTokenStandardAndDetails: this.getTokenStandardAndDetails.bind(this),
// CollectiblesController
addCollectible: collectiblesController.addCollectible.bind(
collectiblesController,
),
// NftController
addNft: nftController.addNft.bind(nftController),
addCollectibleVerifyOwnership:
collectiblesController.addCollectibleVerifyOwnership.bind(
collectiblesController,
),
addNftVerifyOwnership:
nftController.addNftVerifyOwnership.bind(nftController),
removeAndIgnoreCollectible:
collectiblesController.removeAndIgnoreCollectible.bind(
collectiblesController,
),
removeAndIgnoreNft: nftController.removeAndIgnoreNft.bind(nftController),
removeCollectible: collectiblesController.removeCollectible.bind(
collectiblesController,
),
removeNft: nftController.removeNft.bind(nftController),
checkAndUpdateAllCollectiblesOwnershipStatus:
collectiblesController.checkAndUpdateAllCollectiblesOwnershipStatus.bind(
collectiblesController,
),
checkAndUpdateAllNftsOwnershipStatus:
nftController.checkAndUpdateAllNftsOwnershipStatus.bind(nftController),
checkAndUpdateSingleCollectibleOwnershipStatus:
collectiblesController.checkAndUpdateSingleCollectibleOwnershipStatus.bind(
collectiblesController,
checkAndUpdateSingleNftOwnershipStatus:
nftController.checkAndUpdateSingleNftOwnershipStatus.bind(
nftController,
),
isCollectibleOwner: collectiblesController.isCollectibleOwner.bind(
collectiblesController,
),
isNftOwner: nftController.isNftOwner.bind(nftController),
// AddressController
setAddressBook: addressBookController.set.bind(addressBookController),
@ -1948,10 +2005,8 @@ export default class MetamaskController extends EventEmitter {
),
// DetectCollectibleController
detectCollectibles: process.env.COLLECTIBLES_V1
? collectibleDetectionController.detectCollectibles.bind(
collectibleDetectionController,
)
detectNfts: process.env.COLLECTIBLES_V1
? nftDetectionController.detectNfts.bind(nftDetectionController)
: null,
/** Token Detection V2 */
@ -2042,10 +2097,10 @@ export default class MetamaskController extends EventEmitter {
}
}
async addCustomNetwork(customRpc) {
async addCustomNetwork(customRpc, actionId) {
const { chainId, chainName, rpcUrl, ticker, blockExplorerUrl } = customRpc;
await this.preferencesController.addToFrequentRpcList(
this.preferencesController.addToFrequentRpcList(
rpcUrl,
chainId,
ticker,
@ -2078,6 +2133,7 @@ export default class MetamaskController extends EventEmitter {
sensitiveProperties: {
rpc_url: rpcUrlOrigin,
},
actionId,
});
}
@ -2106,6 +2162,8 @@ export default class MetamaskController extends EventEmitter {
///: BEGIN:ONLY_INCLUDE_IN(flask)
// Clear snap state
this.snapController.clearState();
// Clear notification state
this.notificationController.clear();
///: END:ONLY_INCLUDE_IN
// clear accounts in accountTracker
@ -2130,8 +2188,9 @@ export default class MetamaskController extends EventEmitter {
ethQuery,
);
const [primaryKeyring] =
keyringController.getKeyringsByType('HD Key Tree');
const [primaryKeyring] = keyringController.getKeyringsByType(
KEYRING_TYPES.HD_KEY_TREE,
);
if (!primaryKeyring) {
throw new Error('MetamaskController - No HD Key Tree found');
}
@ -2256,9 +2315,12 @@ export default class MetamaskController extends EventEmitter {
});
// Accounts
const [hdKeyring] = this.keyringController.getKeyringsByType('HD Key Tree');
const simpleKeyPairKeyrings =
this.keyringController.getKeyringsByType('Simple Key Pair');
const [hdKeyring] = this.keyringController.getKeyringsByType(
KEYRING_TYPES.HD_KEY_TREE,
);
const simpleKeyPairKeyrings = this.keyringController.getKeyringsByType(
KEYRING_TYPES.IMPORTED,
);
const hdAccounts = await hdKeyring.getAccounts();
const simpleKeyPairKeyringAccounts = await Promise.all(
simpleKeyPairKeyrings.map((keyring) => keyring.getAccounts()),
@ -2355,7 +2417,9 @@ export default class MetamaskController extends EventEmitter {
* Gets the mnemonic of the user's primary keyring.
*/
getPrimaryKeyringMnemonic() {
const [keyring] = this.keyringController.getKeyringsByType('HD Key Tree');
const [keyring] = this.keyringController.getKeyringsByType(
KEYRING_TYPES.HD_KEY_TREE,
);
if (!keyring.mnemonic) {
throw new Error('Primary keyring mnemonic unavailable.');
}
@ -2586,8 +2650,9 @@ export default class MetamaskController extends EventEmitter {
* @returns {} keyState
*/
async addNewAccount(accountCount) {
const [primaryKeyring] =
this.keyringController.getKeyringsByType('HD Key Tree');
const [primaryKeyring] = this.keyringController.getKeyringsByType(
KEYRING_TYPES.HD_KEY_TREE,
);
if (!primaryKeyring) {
throw new Error('MetamaskController - No HD Key Tree found');
}
@ -2630,8 +2695,9 @@ export default class MetamaskController extends EventEmitter {
* encoded as an array of UTF-8 bytes.
*/
async verifySeedPhrase() {
const [primaryKeyring] =
this.keyringController.getKeyringsByType('HD Key Tree');
const [primaryKeyring] = this.keyringController.getKeyringsByType(
KEYRING_TYPES.HD_KEY_TREE,
);
if (!primaryKeyring) {
throw new Error('MetamaskController - No HD Key Tree found');
}
@ -2752,7 +2818,7 @@ export default class MetamaskController extends EventEmitter {
async importAccountWithStrategy(strategy, args) {
const privateKey = await accountImporter.importAccount(strategy, args);
const keyring = await this.keyringController.addNewKeyring(
'Simple Key Pair',
KEYRING_TYPES.IMPORTED,
[privateKey],
);
const [firstAccount] = await keyring.getAccounts();
@ -2760,7 +2826,7 @@ export default class MetamaskController extends EventEmitter {
const allAccounts = await this.keyringController.getAccounts();
this.preferencesController.setAddresses(allAccounts);
// set new account as selected
await this.preferencesController.setSelectedAddress(firstAccount);
this.preferencesController.setSelectedAddress(firstAccount);
}
// ---------------------------------------------------------------------------
@ -3222,7 +3288,7 @@ export default class MetamaskController extends EventEmitter {
customGasSettings,
options,
);
const state = await this.getState();
const state = this.getState();
return state;
}
@ -3245,7 +3311,7 @@ export default class MetamaskController extends EventEmitter {
customGasSettings,
options,
);
const state = await this.getState();
const state = this.getState();
return state;
}

@ -10,10 +10,8 @@ import browser from 'webextension-polyfill';
import { TRANSACTION_STATUSES } from '../../shared/constants/transaction';
import createTxMeta from '../../test/lib/createTxMeta';
import { NETWORK_TYPES } from '../../shared/constants/network';
import {
KEYRING_TYPES,
DEVICE_NAMES,
} from '../../shared/constants/hardware-wallets';
import { KEYRING_TYPES } from '../../shared/constants/keyrings';
import { DEVICE_NAMES } from '../../shared/constants/hardware-wallets';
import { addHexPrefix } from './lib/util';
const Ganache = require('../../test/e2e/ganache');
@ -104,11 +102,14 @@ const CUSTOM_RPC_CHAIN_ID = '0x539';
describe('MetaMaskController', function () {
let metamaskController;
const sandbox = sinon.createSandbox();
const noop = () => undefined;
before(async function () {
globalThis.isFirstTimeProfileLoaded = true;
await ganacheServer.start();
sinon.spy(MetaMaskController.prototype, 'resetStates');
});
beforeEach(function () {
@ -162,6 +163,18 @@ describe('MetaMaskController', function () {
await ganacheServer.quit();
});
describe('should reset states on first time profile load', function () {
it('should reset state', function () {
assert(metamaskController.resetStates.calledOnce);
assert.equal(globalThis.isFirstTimeProfileLoaded, false);
});
it('should not reset states if already set', function () {
// global.isFirstTime should also remain false
assert.equal(globalThis.isFirstTimeProfileLoaded, false);
});
});
describe('#getAccounts', function () {
it('returns first address when dapp calls web3.eth.getAccounts', async function () {
const password = 'a-fake-password';
@ -192,7 +205,7 @@ describe('MetaMaskController', function () {
it('adds private key to keyrings in KeyringController', async function () {
const simpleKeyrings =
metamaskController.keyringController.getKeyringsByType(
'Simple Key Pair',
KEYRING_TYPES.IMPORTED,
);
const privKeyBuffer = simpleKeyrings[0].wallets[0].privateKey;
const pubKeyBuffer = simpleKeyrings[0].wallets[0].publicKey;

@ -0,0 +1,46 @@
import { cloneDeep } from 'lodash';
const version = 76;
/**
* Update to `@metamask/controllers@33.0.0` (rename "Collectible" to "NFT").
*/
export default {
version,
async migrate(originalVersionedData) {
const versionedData = cloneDeep(originalVersionedData);
versionedData.meta.version = version;
const state = versionedData.data;
const newState = transformState(state);
versionedData.data = newState;
return versionedData;
},
};
function transformState(state) {
if (state.CollectiblesController) {
const {
allCollectibleContracts,
allCollectibles,
ignoredCollectibles,
...remainingState
} = state.CollectiblesController;
state.NftController = {
...(allCollectibleContracts
? { allNftContracts: allCollectibleContracts }
: {}),
...(allCollectibles ? { allNfts: allCollectibles } : {}),
...(ignoredCollectibles ? { ignoredNfts: ignoredCollectibles } : {}),
...remainingState,
};
delete state.CollectiblesController;
}
if (state.PreferencesController?.useCollectibleDetection) {
state.PreferencesController.useNftDetection =
state.PreferencesController.useCollectibleDetection;
delete state.PreferencesController.useCollectibleDetection;
}
return state;
}

@ -0,0 +1,143 @@
import migration76 from './076';
describe('migration #76', () => {
it('should update the version metadata', async () => {
const oldStorage = {
meta: {
version: 75,
},
data: {},
};
const newStorage = await migration76.migrate(oldStorage);
expect(newStorage.meta).toStrictEqual({
version: 76,
});
});
it('should migrate known controller state properties', async () => {
const oldStorage = {
meta: {
version: 75,
},
data: {
CollectiblesController: {
allCollectibleContracts: 'foo',
allCollectibles: 'bar',
ignoredCollectibles: 'baz',
},
PreferencesController: {
useCollectibleDetection: 'foobar',
},
},
};
const newStorage = await migration76.migrate(oldStorage);
expect(newStorage).toStrictEqual({
meta: {
version: 76,
},
data: {
NftController: {
allNftContracts: 'foo',
allNfts: 'bar',
ignoredNfts: 'baz',
},
PreferencesController: {
useNftDetection: 'foobar',
},
},
});
});
it('should migrate unknown controller state properties', async () => {
const oldStorage = {
meta: {
version: 75,
},
data: {
CollectiblesController: {
allCollectibleContracts: 'foo',
allCollectibles: 'bar',
ignoredCollectibles: 'baz',
extra: 'extra',
},
PreferencesController: {
extra: 'extra',
useCollectibleDetection: 'foobar',
},
},
};
const newStorage = await migration76.migrate(oldStorage);
expect(newStorage).toStrictEqual({
meta: {
version: 76,
},
data: {
NftController: {
allNftContracts: 'foo',
allNfts: 'bar',
ignoredNfts: 'baz',
extra: 'extra',
},
PreferencesController: {
extra: 'extra',
useNftDetection: 'foobar',
},
},
});
});
it('should handle missing controller state', async () => {
const oldStorage = {
meta: {
version: 75,
},
data: {
CollectiblesController: {
extra: 'extra',
},
PreferencesController: {
extra: 'extra',
},
},
};
const newStorage = await migration76.migrate(oldStorage);
expect(newStorage).toStrictEqual({
meta: {
version: 76,
},
data: {
NftController: {
extra: 'extra',
},
PreferencesController: {
extra: 'extra',
},
},
});
});
it('should handle missing CollectiblesController and PreferencesController', async () => {
const oldStorage = {
meta: {
version: 75,
},
data: {
FooController: { a: 'b' },
},
};
const newStorage = await migration76.migrate(oldStorage);
expect(newStorage).toStrictEqual({
meta: {
version: 76,
},
data: {
FooController: { a: 'b' },
},
});
});
});

@ -0,0 +1,59 @@
import { cloneDeep } from 'lodash';
const version = 77;
/**
* Prior to token detection v2 the data property in tokensChainsCache was an array,
* in v2 we changes that to an object. In this migration we are converting the data as array to object.
*/
export default {
version,
async migrate(originalVersionedData) {
const versionedData = cloneDeep(originalVersionedData);
versionedData.meta.version = version;
const state = versionedData.data;
const newState = transformState(state);
versionedData.data = newState;
return versionedData;
},
};
function transformState(state) {
const TokenListController = state?.TokenListController || {};
const { tokensChainsCache } = TokenListController;
let dataCache;
let dataObject;
// eslint-disable-next-line
for (const chainId in tokensChainsCache) {
dataCache = tokensChainsCache[chainId].data;
dataObject = {};
// if the data is array conver that to object
if (Array.isArray(dataCache)) {
for (const token of dataCache) {
dataObject[token.address] = token;
}
} else if (
Object.keys(dataCache)[0].toLowerCase() !==
dataCache[Object.keys(dataCache)[0]].address.toLowerCase()
) {
// for the users who already updated to the recent version
// and the dataCache is already an object keyed with 0,1,2,3 etc
// eslint-disable-next-line
for (const tokenAddress in dataCache) {
dataObject[dataCache[tokenAddress].address] = dataCache[tokenAddress];
}
}
tokensChainsCache[chainId].data =
Object.keys(dataObject).length > 0 ? dataObject : dataCache;
}
TokenListController.tokensChainsCache = tokensChainsCache;
return {
...state,
TokenListController: {
...TokenListController,
},
};
}

@ -0,0 +1,322 @@
import migration77 from './077';
describe('migration #77', () => {
it('should update the version metadata', async () => {
const oldStorage = {
meta: {
version: 76,
},
};
const newStorage = await migration77.migrate(oldStorage);
expect(newStorage.meta).toStrictEqual({
version: 77,
});
});
it('should change the data from array to object for a single network', async () => {
const oldStorage = {
meta: {
version: 76,
},
data: {
TokenListController: {
tokenList: {
'0x514910771af9ca656af840dff83e8264ecf986ca': {
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
},
},
tokensChainsCache: {
1: {
timestamp: 1234,
data: [
{
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
},
{
address: '0xc00e94cb662c3520282e6f5717214004a7f26888',
symbol: 'COMP',
decimals: 18,
},
],
},
},
},
},
};
const newStorage = await migration77.migrate(oldStorage);
expect(newStorage).toStrictEqual({
meta: {
version: 77,
},
data: {
TokenListController: {
tokenList: {
'0x514910771af9ca656af840dff83e8264ecf986ca': {
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
},
},
tokensChainsCache: {
1: {
timestamp: 1234,
data: {
'0x514910771af9ca656af840dff83e8264ecf986ca': {
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
},
'0xc00e94cb662c3520282e6f5717214004a7f26888': {
address: '0xc00e94cb662c3520282e6f5717214004a7f26888',
symbol: 'COMP',
decimals: 18,
},
},
},
},
},
},
});
});
it('should change the data from array to object for a multiple networks', async () => {
const oldStorage = {
meta: {
version: 76,
},
data: {
TokenListController: {
tokenList: {
'0x514910771af9ca656af840dff83e8264ecf986ca': {
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
},
},
tokensChainsCache: {
1: {
timestamp: 1234,
data: [
{
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
},
{
address: '0xc00e94cb662c3520282e6f5717214004a7f26888',
symbol: 'COMP',
decimals: 18,
},
],
},
56: {
timestamp: 1324,
data: [
{
address: '0x3ee2200efb3400fabb9aacf31297cbdd1d435d47',
symbol: 'ADA',
decimals: 18,
},
{
address: '0x928e55dab735aa8260af3cedada18b5f70c72f1b',
symbol: 'FRONT',
decimals: 18,
},
],
},
},
},
},
};
const newStorage = await migration77.migrate(oldStorage);
expect(newStorage).toStrictEqual({
meta: {
version: 77,
},
data: {
TokenListController: {
tokenList: {
'0x514910771af9ca656af840dff83e8264ecf986ca': {
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
},
},
tokensChainsCache: {
1: {
timestamp: 1234,
data: {
'0x514910771af9ca656af840dff83e8264ecf986ca': {
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
},
'0xc00e94cb662c3520282e6f5717214004a7f26888': {
address: '0xc00e94cb662c3520282e6f5717214004a7f26888',
symbol: 'COMP',
decimals: 18,
},
},
},
56: {
timestamp: 1324,
data: {
'0x3ee2200efb3400fabb9aacf31297cbdd1d435d47': {
address: '0x3ee2200efb3400fabb9aacf31297cbdd1d435d47',
symbol: 'ADA',
decimals: 18,
},
'0x928e55dab735aa8260af3cedada18b5f70c72f1b': {
address: '0x928e55dab735aa8260af3cedada18b5f70c72f1b',
symbol: 'FRONT',
decimals: 18,
},
},
},
},
},
},
});
});
it('should not change anything if the data is already an object', async () => {
const oldStorage = {
meta: {
version: 76,
},
data: {
TokenListController: {
tokenList: {
'0x514910771af9ca656af840dff83e8264ecf986ca': {
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
},
},
tokensChainsCache: {
1: {
timestamp: 1234,
data: {
'0x514910771af9ca656af840dff83e8264ecf986ca': {
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
},
'0xc00e94cb662c3520282e6f5717214004a7f26888': {
address: '0xc00e94cb662c3520282e6f5717214004a7f26888',
symbol: 'COMP',
decimals: 18,
},
},
},
},
},
},
};
const newStorage = await migration77.migrate(oldStorage);
expect(newStorage).toStrictEqual({
meta: {
version: 77,
},
data: {
TokenListController: {
tokenList: {
'0x514910771af9ca656af840dff83e8264ecf986ca': {
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
},
},
tokensChainsCache: {
1: {
timestamp: 1234,
data: {
'0x514910771af9ca656af840dff83e8264ecf986ca': {
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
},
'0xc00e94cb662c3520282e6f5717214004a7f26888': {
address: '0xc00e94cb662c3520282e6f5717214004a7f26888',
symbol: 'COMP',
decimals: 18,
},
},
},
},
},
},
});
});
it('should correct the address keys if the object is keyed wrong', async () => {
const oldStorage = {
meta: {
version: 76,
},
data: {
TokenListController: {
tokenList: {
'0x514910771af9ca656af840dff83e8264ecf986ca': {
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
},
},
tokensChainsCache: {
1: {
timestamp: 1234,
data: {
0: {
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
},
1: {
address: '0xc00e94cb662c3520282e6f5717214004a7f26888',
symbol: 'COMP',
decimals: 18,
},
},
},
},
},
},
};
const newStorage = await migration77.migrate(oldStorage);
expect(newStorage).toStrictEqual({
meta: {
version: 77,
},
data: {
TokenListController: {
tokenList: {
'0x514910771af9ca656af840dff83e8264ecf986ca': {
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
},
},
tokensChainsCache: {
1: {
timestamp: 1234,
data: {
'0x514910771af9ca656af840dff83e8264ecf986ca': {
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
},
'0xc00e94cb662c3520282e6f5717214004a7f26888': {
address: '0xc00e94cb662c3520282e6f5717214004a7f26888',
symbol: 'COMP',
decimals: 18,
},
},
},
},
},
},
});
});
});

@ -79,6 +79,8 @@ import m072 from './072';
import m073 from './073';
import m074 from './074';
import m075 from './075';
import m076 from './076';
import m077 from './077';
const migrations = [
m002,
@ -155,6 +157,8 @@ const migrations = [
m073,
m074,
m075,
m076,
m077,
];
export default migrations;

@ -1,7 +1,7 @@
import browser from 'webextension-polyfill';
import { getBlockExplorerLink } from '@metamask/etherscan-link';
import { getEnvironmentType, checkForError } from '../lib/util';
import { getEnvironmentType } from '../lib/util';
import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../shared/constants/app';
import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction';
@ -13,70 +13,32 @@ export default class ExtensionPlatform {
browser.runtime.reload();
}
openTab(options) {
return new Promise((resolve, reject) => {
browser.tabs.create(options).then((newTab) => {
const error = checkForError();
if (error) {
return reject(error);
}
return resolve(newTab);
});
});
async openTab(options) {
const newTab = await browser.tabs.create(options);
return newTab;
}
openWindow(options) {
return new Promise((resolve, reject) => {
browser.windows.create(options).then((newWindow) => {
const error = checkForError();
if (error) {
return reject(error);
}
return resolve(newWindow);
});
});
async openWindow(options) {
const newWindow = await browser.windows.create(options);
return newWindow;
}
focusWindow(windowId) {
return new Promise((resolve, reject) => {
browser.windows.update(windowId, { focused: true }).then(() => {
const error = checkForError();
if (error) {
return reject(error);
}
return resolve();
});
});
async focusWindow(windowId) {
await browser.windows.update(windowId, { focused: true });
}
updateWindowPosition(windowId, left, top) {
return new Promise((resolve, reject) => {
browser.windows.update(windowId, { left, top }).then(() => {
const error = checkForError();
if (error) {
return reject(error);
}
return resolve();
});
});
async updateWindowPosition(windowId, left, top) {
await browser.windows.update(windowId, { left, top });
}
getLastFocusedWindow() {
return new Promise((resolve, reject) => {
browser.windows.getLastFocused().then((windowObject) => {
const error = checkForError();
if (error) {
return reject(error);
}
return resolve(windowObject);
});
});
async getLastFocusedWindow() {
const windowObject = await browser.windows.getLastFocused();
return windowObject;
}
closeCurrentWindow() {
return browser.windows.getCurrent().then((windowDetails) => {
return browser.windows.remove(windowDetails.id);
});
async closeCurrentWindow() {
const windowDetails = await browser.windows.getCurrent();
browser.windows.remove(windowDetails.id);
}
getVersion() {
@ -169,67 +131,28 @@ export default class ExtensionPlatform {
browser.windows.onRemoved.addListener(listener);
}
getAllWindows() {
return new Promise((resolve, reject) => {
browser.windows.getAll().then((windows) => {
const error = checkForError();
if (error) {
return reject(error);
}
return resolve(windows);
});
});
async getAllWindows() {
const windows = await browser.windows.getAll();
return windows;
}
getActiveTabs() {
return new Promise((resolve, reject) => {
browser.tabs.query({ active: true }).then((tabs) => {
const error = checkForError();
if (error) {
return reject(error);
}
return resolve(tabs);
});
});
async getActiveTabs() {
const tabs = await browser.tabs.query({ active: true });
return tabs;
}
currentTab() {
return new Promise((resolve, reject) => {
browser.tabs.getCurrent().then((tab) => {
const err = checkForError();
if (err) {
reject(err);
} else {
resolve(tab);
}
});
});
async currentTab() {
const tab = await browser.tabs.getCurrent();
return tab;
}
switchToTab(tabId) {
return new Promise((resolve, reject) => {
browser.tabs.update(tabId, { highlighted: true }).then((tab) => {
const err = checkForError();
if (err) {
reject(err);
} else {
resolve(tab);
}
});
});
async switchToTab(tabId) {
const tab = await browser.tabs.update(tabId, { highlighted: true });
return tab;
}
closeTab(tabId) {
return new Promise((resolve, reject) => {
browser.tabs.remove(tabId).then(() => {
const err = checkForError();
if (err) {
reject(err);
} else {
resolve();
}
});
});
async closeTab(tabId) {
await browser.tabs.remove(tabId);
}
_showConfirmedTransaction(txMeta, rpcPrefs) {

@ -1,10 +1,10 @@
import setupSentry from './lib/setupSentry';
// The root compartment will populate this with hooks
global.sentryHooks = {};
global.stateHooks = {};
// setup sentry error reporting
global.sentry = setupSentry({
release: process.env.METAMASK_VERSION,
getState: () => global.sentryHooks?.getSentryState?.() || {},
getState: () => global.stateHooks?.getSentryState?.() || {},
});

@ -15,6 +15,7 @@ import launchMetaMaskUi, { updateBackgroundConnection } from '../../ui';
import {
ENVIRONMENT_TYPE_FULLSCREEN,
ENVIRONMENT_TYPE_POPUP,
EXTENSION_MESSAGES,
PLATFORM_FIREFOX,
} from '../../shared/constants/app';
import { isManifestV3 } from '../../shared/modules/mv3.utils';
@ -29,14 +30,22 @@ const container = document.getElementById('app-content');
const ONE_SECOND_IN_MILLISECONDS = 1_000;
// Service Worker Keep Alive Message Constants
const WORKER_KEEP_ALIVE_INTERVAL = ONE_SECOND_IN_MILLISECONDS;
const WORKER_KEEP_ALIVE_MESSAGE = 'WORKER_KEEP_ALIVE_MESSAGE';
const ACK_KEEP_ALIVE_WAIT_TIME = 60_000; // 1 minute
const ACK_KEEP_ALIVE_MESSAGE = 'ACK_KEEP_ALIVE_MESSAGE';
// Timeout for initializing phishing warning page.
const PHISHING_WARNING_PAGE_TIMEOUT = ONE_SECOND_IN_MILLISECONDS;
const PHISHING_WARNING_SW_STORAGE_KEY = 'phishing-warning-sw-registered';
let lastMessageReceivedTimestamp = Date.now();
let extensionPort;
let ackTimeoutToDisplayError;
/*
* As long as UI is open it will keep sending messages to service worker
* In service worker as this message is received
@ -44,8 +53,45 @@ const PHISHING_WARNING_SW_STORAGE_KEY = 'phishing-warning-sw-registered';
* Time has been kept to 1000ms but can be reduced for even faster re-activation of service worker
*/
if (isManifestV3) {
setInterval(() => {
// Checking for SW aliveness (or stuckness) flow
// 1. Check if we have an extensionPort, if yes
// 2a. Send a keep alive message to the background via extensionPort
// 2b. Add a listener to it (if not already added)
// 3a. Set a timeout to check if we have received an ACK from background
// 3b. If we have not received an ACK within ACK_KEEP_ALIVE_WAIT_TIME,
// we know the background is stuck or dead
// 4. If we recieve an ACK_KEEP_ALIVE_MESSAGE from the service worker, we know it is alive
const ackKeepAliveListener = (message) => {
if (message.name === ACK_KEEP_ALIVE_MESSAGE) {
lastMessageReceivedTimestamp = Date.now();
clearTimeout(ackTimeoutToDisplayError);
}
};
const keepAliveInterval = setInterval(() => {
browser.runtime.sendMessage({ name: WORKER_KEEP_ALIVE_MESSAGE });
if (extensionPort !== null && extensionPort !== undefined) {
extensionPort.postMessage({ name: WORKER_KEEP_ALIVE_MESSAGE });
if (extensionPort.onMessage.hasListener(ackKeepAliveListener) === false) {
extensionPort.onMessage.addListener(ackKeepAliveListener);
}
}
ackTimeoutToDisplayError = setTimeout(() => {
if (
Date.now() - lastMessageReceivedTimestamp >
ACK_KEEP_ALIVE_WAIT_TIME
) {
clearInterval(keepAliveInterval);
displayCriticalError(
'somethingIsWrong',
new Error("Something's gone wrong. Try reloading the page."),
);
}
}, ACK_KEEP_ALIVE_WAIT_TIME);
}, WORKER_KEEP_ALIVE_INTERVAL);
}
@ -61,16 +107,13 @@ async function start() {
let isUIInitialised = false;
// setup stream to background
let extensionPort = browser.runtime.connect({ name: windowType });
extensionPort = browser.runtime.connect({ name: windowType });
let connectionStream = new PortStream(extensionPort);
const activeTab = await queryCurrentActiveTab(windowType);
let loadPhishingWarningPage;
/**
* In case of MV3 the issue of blank screen was very frequent, it is caused by UI initialising before background is ready to send state.
* Code below ensures that UI is rendered only after background is ready.
*/
if (isManifestV3) {
/*
* In case of MV3 the issue of blank screen was very frequent, it is caused by UI initialising before background is ready to send state.
@ -78,7 +121,7 @@ async function start() {
* In case the UI is already rendered, only update the streams.
*/
const messageListener = async (message) => {
if (message?.name === 'CONNECTION_READY') {
if (message?.name === EXTENSION_MESSAGES.CONNECTION_READY) {
if (isUIInitialised) {
// Currently when service worker is revived we create new streams
// in later version we might try to improve it by reviving same streams.
@ -104,10 +147,8 @@ async function start() {
* worker has been registered, so that the warning page works offline.
*/
loadPhishingWarningPage = async function () {
const currentPlatform = getPlatform();
// Check session storage for whether we've already initalized the phishing warning
// service worker this browser session and do not attempt to re-initialize if so.
// Check session storage for whether we've already initialized the phishing warning
// service worker in this browser session and do not attempt to re-initialize if so.
const phishingSWMemoryFetch = await browser.storage.session.get(
PHISHING_WARNING_SW_STORAGE_KEY,
);
@ -116,7 +157,9 @@ async function start() {
return;
}
const currentPlatform = getPlatform();
let iframe;
try {
const extensionStartupPhishingPageUrl = new URL(
process.env.PHISHING_WARNING_PAGE_URL,
@ -152,7 +195,9 @@ async function start() {
() => deferredReject(new PhishingWarningPageTimeoutError()),
PHISHING_WARNING_PAGE_TIMEOUT,
);
await loadComplete;
// store a flag in sessions storage that we've already loaded the service worker
// and don't need to try again
if (currentPlatform === PLATFORM_FIREFOX) {
@ -182,12 +227,13 @@ async function start() {
};
// resetExtensionStreamAndListeners takes care to remove listeners from closed streams
// it also creates new streams and attach event listeners to them
// it also creates new streams and attaches event listeners to them
const resetExtensionStreamAndListeners = () => {
extensionPort.onMessage.removeListener(messageListener);
extensionPort.onDisconnect.removeListener(
resetExtensionStreamAndListeners,
);
// message below will try to activate service worker
// in MV3 is likely that reason of stream closing is service worker going in-active
browser.runtime.sendMessage({ name: WORKER_KEEP_ALIVE_MESSAGE });
@ -208,7 +254,7 @@ async function start() {
initializeUi(tab, connectionStream, (err, store) => {
if (err) {
// if there's an error, store will be = metamaskState
displayCriticalError(err, store);
displayCriticalError('troubleStarting', err, store);
return;
}
isUIInitialised = true;
@ -226,7 +272,7 @@ async function start() {
function updateUiStreams() {
connectToAccountManager(connectionStream, (err, backgroundConnection) => {
if (err) {
displayCriticalError(err);
displayCriticalError('troubleStarting', err);
return;
}
@ -236,27 +282,22 @@ async function start() {
}
async function queryCurrentActiveTab(windowType) {
return new Promise((resolve) => {
// At the time of writing we only have the `activeTab` permission which means
// that this query will only succeed in the popup context (i.e. after a "browserAction")
if (windowType !== ENVIRONMENT_TYPE_POPUP) {
resolve({});
return;
}
// At the time of writing we only have the `activeTab` permission which means
// that this query will only succeed in the popup context (i.e. after a "browserAction")
if (windowType !== ENVIRONMENT_TYPE_POPUP) {
return {};
}
browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
const [activeTab] = tabs;
const { id, title, url } = activeTab;
const { origin, protocol } = url ? new URL(url) : {};
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
const [activeTab] = tabs;
const { id, title, url } = activeTab;
const { origin, protocol } = url ? new URL(url) : {};
if (!origin || origin === 'null') {
resolve({});
return;
}
if (!origin || origin === 'null') {
return {};
}
resolve({ id, title, origin, protocol, url });
});
});
return { id, title, origin, protocol, url };
}
function initializeUi(activeTab, connectionStream, cb) {
@ -277,8 +318,8 @@ function initializeUi(activeTab, connectionStream, cb) {
});
}
async function displayCriticalError(err, metamaskState) {
const html = await getErrorHtml(SUPPORT_LINK, metamaskState);
async function displayCriticalError(errorKey, err, metamaskState) {
const html = await getErrorHtml(errorKey, SUPPORT_LINK, metamaskState);
container.innerHTML = html;

@ -71,7 +71,7 @@ async function defineAndRunBuildTasks() {
version,
} = await parseArgv();
const browserPlatforms = ['firefox', 'chrome', 'brave', 'opera'];
const browserPlatforms = ['firefox', 'chrome'];
const browserVersionMap = getBrowserVersionMap(browserPlatforms, version);

@ -58,10 +58,8 @@ function createStyleTasks({ livereload }) {
};
async function buildScss() {
await Promise.all([
buildScssPipeline(src, dest, devMode, false),
buildScssPipeline(src, dest, devMode, true),
]);
await buildScssPipeline(src, dest, devMode, false);
await buildScssPipeline(src, dest, devMode, true);
}
}
}

@ -4,7 +4,7 @@
* Reads all the icon svg files in app/images/icons
* and returns an object of icon name key value pairs
* stored in the environment variable ICON_NAMES
* Used with the Icon component in ./ui/component-library/icon
* Used with the Icon component in ./ui/components/component-library/icon/icon.js
*/
const fs = require('fs');
const path = require('path');
@ -22,12 +22,12 @@ const getIconNameInSnakeCase = (fileName) =>
.replace(/-/gu, '_')
.toUpperCase();
const generateIconNames = async () => {
const generateIconNames = () => {
const iconNames = {};
const svgIconsFolderPath = path.join(__dirname, `../${SVG_ICONS_FOLDER}`);
const fileList = await fs.promises.readdir(svgIconsFolderPath);
const fileList = fs.readdirSync(svgIconsFolderPath);
const svgIconsFileList = fileList.filter(
(fileName) => path.extname(fileName) === ASSET_EXT,
@ -39,7 +39,9 @@ const generateIconNames = async () => {
getIconNameKebabCase(fileName)),
);
return iconNames;
const iconNamesStringified = JSON.stringify(iconNames);
return iconNamesStringified;
};
module.exports = { generateIconNames };

@ -23,6 +23,8 @@ async function start() {
console.log('CIRCLE_BUILD_NUM', CIRCLE_BUILD_NUM);
const { CIRCLE_WORKFLOW_JOB_ID } = process.env;
console.log('CIRCLE_WORKFLOW_JOB_ID', CIRCLE_WORKFLOW_JOB_ID);
const { PARENT_COMMIT } = process.env;
console.log('PARENT_COMMIT', PARENT_COMMIT);
if (!CIRCLE_PULL_REQUEST) {
console.warn(`No pull request detected for commit "${CIRCLE_SHA1}"`);
@ -36,7 +38,7 @@ async function start() {
// build the github comment content
// links to extension builds
const platforms = ['chrome', 'firefox', 'opera'];
const platforms = ['chrome', 'firefox'];
const buildLinks = platforms
.map((platform) => {
const url = `${BUILD_LINK_BASE}/builds/metamask-${platform}-${VERSION}.zip`;
@ -87,6 +89,9 @@ async function start() {
.map((key) => `<li>${key}: ${bundles[key].join(', ')}</li>`)
.join('')}</ul>`;
const bundleSizeDataUrl =
'https://raw.githubusercontent.com/MetaMask/extension_bundlesize_stats/main/stats/bundle_size_data.json';
const coverageUrl = `${BUILD_LINK_BASE}/coverage/index.html`;
const coverageLink = `<a href="${coverageUrl}">Report</a>`;
@ -243,6 +248,67 @@ async function start() {
console.log(`No results for ${summaryPlatform} found; skipping benchmark`);
}
try {
const prBundleSizeStats = JSON.parse(
await fs.readFile(
path.resolve(
__dirname,
'..',
path.join('test-artifacts', 'chrome', 'mv3', 'bundle_size.json'),
),
'utf-8',
),
);
const devBundleSizeStats = await (
await fetch(bundleSizeDataUrl, {
method: 'GET',
})
).json();
const prSizes = {
background: prBundleSizeStats.background.size,
ui: prBundleSizeStats.ui.size,
common: prBundleSizeStats.common.size,
};
const devSizes = Object.keys(prSizes).reduce((sizes, part) => {
sizes[part] = devBundleSizeStats[PARENT_COMMIT][part] || 0;
return sizes;
}, {});
const diffs = Object.keys(prSizes).reduce((output, part) => {
output[part] = prSizes[part] - devSizes[part];
return output;
}, {});
const sizeDiffRows = Object.keys(diffs).map(
(part) => `${part}: ${diffs[part]} bytes`,
);
const sizeDiffHiddenContent = `<ul>${sizeDiffRows
.map((row) => `<li>${row}</li>`)
.join('\n')}</ul>`;
const sizeDiff = diffs.background + diffs.common;
const sizeDiffWarning =
sizeDiff > 0
? `🚨 Warning! Bundle size has increased!`
: `🚀 Bundle size reduced!`;
const sizeDiffExposedContent =
sizeDiff === 0
? `Bundle size diffs`
: `Bundle size diffs [${sizeDiffWarning}]`;
const sizeDiffBody = `<details><summary>${sizeDiffExposedContent}</summary>${sizeDiffHiddenContent}</details>\n\n`;
commentBody += sizeDiffBody;
} catch (error) {
console.error(`Error constructing bundle size diffs results: '${error}'`);
}
try {
const highlights = await getHighlights({ artifactBase: BUILD_LINK_BASE });
if (highlights) {

@ -698,7 +698,7 @@
"ui/components/app/signature-request/signature-request-header/signature-request-header.component.js",
"ui/components/app/signature-request/signature-request-header/signature-request-header.stories.js",
"ui/components/app/signature-request/signature-request-message/index.js",
"ui/components/app/signature-request/signature-request-message/signature-request-message.component.js",
"ui/components/app/signature-request/signature-request-message/signature-request-message.js",
"ui/components/app/signature-request/signature-request.component.js",
"ui/components/app/signature-request/signature-request.component.test.js",
"ui/components/app/signature-request/signature-request.container.js",

@ -2,6 +2,7 @@
Steps to mark a full pass of QA complete.
* Browsers: Opera, Chrome, Firefox, Edge.
* Use the Chrome build for all Chromium-derived browsers (e.g. Opera and Edge)
* OS: Ubuntu, Mac OSX, Windows
* Load older version of MetaMask and attempt to simulate updating the extension.
* Open Developer Console in background and popup, inspect errors.

@ -4,12 +4,12 @@ Fixture data can be generated by following these steps:
1. Load the unpacked extension in development or test mode
2. Inspecting the background context of the extension
3. Call `metamaskGetState`, then call [`copy`][1] on the results
3. Call `stateHooks.metamaskGetState`, then call [`copy`][1] on the results
You can then paste the contents directly in your fixture file.
```js
copy(await metamaskGetState())
copy(await stateHooks.metamaskGetState())
```

@ -41,7 +41,11 @@ module.exports = {
// TODO: enable resetMocks
// resetMocks: true,
restoreMocks: true,
setupFiles: ['<rootDir>/test/setup.js', '<rootDir>/test/env.js'],
setupFiles: [
'<rootDir>/test/setup.js',
'<rootDir>/test/env.js',
'<rootDir>/test/jest/env.js', // jest specific env vars that break mocha tests
],
setupFilesAfterEnv: ['<rootDir>/test/jest/setup.js'],
testMatch: [
'<rootDir>/ui/**/*.test.js',

@ -1,20 +0,0 @@
/* eslint-disable import/unambiguous */
module.exports = {
coverageDirectory: './jest-coverage/storybook',
coverageReporters: ['html', 'text-summary'],
// TODO: enable resetMocks
// resetMocks: true,
restoreMocks: true,
setupFiles: ['<rootDir>/test/setup.js', '<rootDir>/test/env.js'],
setupFilesAfterEnv: ['<rootDir>/test/jest/setup.js'],
testMatch: ['<rootDir>/ui/**/*stories.test.js'],
testTimeout: 2500,
transform: {
'^.+\\.[tj]sx?$': 'babel-jest',
'^.+\\.mdx$': '@storybook/addon-docs/jest-transform-mdx',
},
testEnvironment: 'jsdom',
testEnvironmentOptions: {
customExportConditions: ['node', 'node-addons'],
},
};

@ -1238,14 +1238,145 @@
"packages": {
"@ethersproject/bignumber": true,
"@ethersproject/bignumber>@ethersproject/bytes": true,
"@metamask/controllers": true,
"@metamask/controllers>@ethersproject/providers": true,
"@metamask/controllers>isomorphic-fetch": true,
"@metamask/smart-transactions-controller>@metamask/controllers": true,
"@metamask/smart-transactions-controller>bignumber.js": true,
"@metamask/smart-transactions-controller>fast-json-patch": true,
"lodash": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers": {
"globals": {
"Headers": true,
"URL": true,
"clearInterval": true,
"clearTimeout": true,
"console.error": true,
"console.log": true,
"fetch": true,
"setInterval": true,
"setTimeout": true
},
"packages": {
"@ethereumjs/common": true,
"@ethereumjs/tx": true,
"@metamask/contract-metadata": true,
"@metamask/controllers>@ethersproject/abi": true,
"@metamask/controllers>@ethersproject/contracts": true,
"@metamask/controllers>@ethersproject/providers": true,
"@metamask/controllers>abort-controller": true,
"@metamask/controllers>async-mutex": true,
"@metamask/controllers>eth-json-rpc-infura": true,
"@metamask/controllers>eth-phishing-detect": true,
"@metamask/controllers>isomorphic-fetch": true,
"@metamask/controllers>multiformats": true,
"@metamask/controllers>web3": true,
"@metamask/controllers>web3-provider-engine": true,
"@metamask/metamask-eth-abis": true,
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry": true,
"@metamask/smart-transactions-controller>@metamask/controllers>ethereumjs-wallet": true,
"@metamask/smart-transactions-controller>@metamask/controllers>nanoid": true,
"browserify>buffer": true,
"browserify>events": true,
"deep-freeze-strict": true,
"eslint>fast-deep-equal": true,
"eth-ens-namehash": true,
"eth-keyring-controller": true,
"eth-query": true,
"eth-rpc-errors": true,
"eth-sig-util": true,
"ethereumjs-util": true,
"ethjs>ethjs-unit": true,
"immer": true,
"json-rpc-engine": true,
"jsonschema": true,
"punycode": true,
"single-call-balance-checker-abi": true,
"uuid": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry": {
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs": {
"globals": {
"clearInterval": true,
"setInterval": true
},
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>bn.js": true,
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-abi": true,
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract": true,
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-query": true,
"browserify>buffer": true,
"ethjs>ethjs-filter": true,
"ethjs>ethjs-provider-http": true,
"ethjs>ethjs-unit": true,
"ethjs>ethjs-util": true,
"ethjs>js-sha3": true,
"ethjs>number-to-bn": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-abi": {
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>bn.js": true,
"browserify>buffer": true,
"ethjs>js-sha3": true,
"ethjs>number-to-bn": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract": {
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract>ethjs-abi": true,
"ethjs-query>babel-runtime": true,
"ethjs>ethjs-filter": true,
"ethjs>ethjs-util": true,
"ethjs>js-sha3": true,
"promise-to-callback": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract>ethjs-abi": {
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>bn.js": true,
"browserify>buffer": true,
"ethjs>js-sha3": true,
"ethjs>number-to-bn": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-query": {
"globals": {
"console": true
},
"packages": {
"ethjs-query>babel-runtime": true,
"ethjs-query>ethjs-format": true,
"ethjs-query>ethjs-rpc": true,
"promise-to-callback": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>ethereumjs-wallet": {
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>ethereumjs-wallet>uuid": true,
"@truffle/codec>utf8": true,
"browserify>buffer": true,
"browserify>crypto-browserify": true,
"ethereumjs-util": true,
"ethereumjs-util>ethereum-cryptography": true,
"ethereumjs-wallet>aes-js": true,
"ethereumjs-wallet>bs58check": true,
"ethereumjs-wallet>randombytes": true,
"ethers>@ethersproject/json-wallets>scrypt-js": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>ethereumjs-wallet>uuid": {
"globals": {
"crypto": true,
"msCrypto": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>nanoid": {
"globals": {
"crypto.getRandomValues": true
@ -1265,7 +1396,7 @@
"setTimeout": true
}
},
"@metamask/snap-controllers>nanoid": {
"@metamask/snaps-controllers>nanoid": {
"globals": {
"crypto.getRandomValues": true
}
@ -2223,7 +2354,7 @@
"TextEncoder": true
},
"packages": {
"@metamask/snap-utils>superstruct": true,
"@metamask/snaps-utils>superstruct": true,
"browserify>buffer": true,
"nock>debug": true
}

@ -1366,6 +1366,50 @@
"watchify>xtend": true
}
},
"@metamask/post-message-stream": {
"globals": {
"WorkerGlobalScope": true,
"addEventListener": true,
"location.origin": true,
"onmessage": "write",
"postMessage": true,
"removeEventListener": true
},
"packages": {
"@metamask/post-message-stream>@metamask/utils": true,
"@metamask/post-message-stream>readable-stream": true
}
},
"@metamask/post-message-stream>@metamask/utils": {
"packages": {
"eslint>fast-deep-equal": true
}
},
"@metamask/post-message-stream>readable-stream": {
"packages": {
"@metamask/post-message-stream>readable-stream>safe-buffer": true,
"@metamask/post-message-stream>readable-stream>string_decoder": true,
"@storybook/api>util-deprecate": true,
"browserify>browser-resolve": true,
"browserify>events": true,
"browserify>process": true,
"browserify>timers-browserify": true,
"pumpify>inherits": true,
"readable-stream>core-util-is": true,
"readable-stream>isarray": true,
"vinyl>cloneable-readable>process-nextick-args": true
}
},
"@metamask/post-message-stream>readable-stream>safe-buffer": {
"packages": {
"browserify>buffer": true
}
},
"@metamask/post-message-stream>readable-stream>string_decoder": {
"packages": {
"@metamask/post-message-stream>readable-stream>safe-buffer": true
}
},
"@metamask/providers>@metamask/object-multiplex": {
"globals": {
"console.warn": true
@ -1384,8 +1428,9 @@
"@metamask/rpc-methods>@metamask/controllers": true,
"@metamask/rpc-methods>@metamask/key-tree": true,
"@metamask/rpc-methods>nanoid": true,
"@metamask/snap-utils": true,
"@metamask/snap-utils>superstruct": true,
"@metamask/snaps-utils": true,
"@metamask/snaps-utils>@noble/hashes": true,
"@metamask/snaps-utils>superstruct": true,
"eth-block-tracker>@metamask/utils": true,
"eth-rpc-errors": true
}
@ -1526,9 +1571,9 @@
"@metamask/rpc-methods>@metamask/key-tree>@noble/ed25519": true,
"@metamask/rpc-methods>@metamask/key-tree>@noble/secp256k1": true,
"@metamask/rpc-methods>@metamask/key-tree>@scure/bip39": true,
"@metamask/snap-utils>@noble/hashes": true,
"@metamask/snap-utils>@scure/base": true,
"browserify>buffer": true
"@metamask/snaps-utils>@noble/hashes": true,
"@metamask/snaps-utils>@scure/base": true,
"eth-block-tracker>@metamask/utils": true
}
},
"@metamask/rpc-methods>@metamask/key-tree>@noble/ed25519": {
@ -1549,8 +1594,8 @@
},
"@metamask/rpc-methods>@metamask/key-tree>@scure/bip39": {
"packages": {
"@metamask/snap-utils>@noble/hashes": true,
"@metamask/snap-utils>@scure/base": true
"@metamask/snaps-utils>@noble/hashes": true,
"@metamask/snaps-utils>@scure/base": true
}
},
"@metamask/rpc-methods>nanoid": {
@ -1570,14 +1615,145 @@
"packages": {
"@ethersproject/bignumber": true,
"@ethersproject/bignumber>@ethersproject/bytes": true,
"@metamask/controllers": true,
"@metamask/controllers>@ethersproject/providers": true,
"@metamask/controllers>isomorphic-fetch": true,
"@metamask/smart-transactions-controller>@metamask/controllers": true,
"@metamask/smart-transactions-controller>bignumber.js": true,
"@metamask/smart-transactions-controller>fast-json-patch": true,
"lodash": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers": {
"globals": {
"Headers": true,
"URL": true,
"clearInterval": true,
"clearTimeout": true,
"console.error": true,
"console.log": true,
"fetch": true,
"setInterval": true,
"setTimeout": true
},
"packages": {
"@ethereumjs/common": true,
"@ethereumjs/tx": true,
"@metamask/contract-metadata": true,
"@metamask/controllers>@ethersproject/abi": true,
"@metamask/controllers>@ethersproject/contracts": true,
"@metamask/controllers>@ethersproject/providers": true,
"@metamask/controllers>abort-controller": true,
"@metamask/controllers>async-mutex": true,
"@metamask/controllers>eth-json-rpc-infura": true,
"@metamask/controllers>eth-phishing-detect": true,
"@metamask/controllers>isomorphic-fetch": true,
"@metamask/controllers>multiformats": true,
"@metamask/controllers>web3": true,
"@metamask/controllers>web3-provider-engine": true,
"@metamask/metamask-eth-abis": true,
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry": true,
"@metamask/smart-transactions-controller>@metamask/controllers>ethereumjs-wallet": true,
"@metamask/smart-transactions-controller>@metamask/controllers>nanoid": true,
"browserify>buffer": true,
"browserify>events": true,
"deep-freeze-strict": true,
"eslint>fast-deep-equal": true,
"eth-ens-namehash": true,
"eth-keyring-controller": true,
"eth-query": true,
"eth-rpc-errors": true,
"eth-sig-util": true,
"ethereumjs-util": true,
"ethjs>ethjs-unit": true,
"immer": true,
"json-rpc-engine": true,
"jsonschema": true,
"punycode": true,
"single-call-balance-checker-abi": true,
"uuid": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry": {
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs": {
"globals": {
"clearInterval": true,
"setInterval": true
},
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>bn.js": true,
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-abi": true,
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract": true,
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-query": true,
"browserify>buffer": true,
"ethjs>ethjs-filter": true,
"ethjs>ethjs-provider-http": true,
"ethjs>ethjs-unit": true,
"ethjs>ethjs-util": true,
"ethjs>js-sha3": true,
"ethjs>number-to-bn": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-abi": {
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>bn.js": true,
"browserify>buffer": true,
"ethjs>js-sha3": true,
"ethjs>number-to-bn": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract": {
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract>ethjs-abi": true,
"ethjs-query>babel-runtime": true,
"ethjs>ethjs-filter": true,
"ethjs>ethjs-util": true,
"ethjs>js-sha3": true,
"promise-to-callback": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract>ethjs-abi": {
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>bn.js": true,
"browserify>buffer": true,
"ethjs>js-sha3": true,
"ethjs>number-to-bn": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-query": {
"globals": {
"console": true
},
"packages": {
"ethjs-query>babel-runtime": true,
"ethjs-query>ethjs-format": true,
"ethjs-query>ethjs-rpc": true,
"promise-to-callback": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>ethereumjs-wallet": {
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>ethereumjs-wallet>uuid": true,
"@truffle/codec>utf8": true,
"browserify>buffer": true,
"browserify>crypto-browserify": true,
"ethereumjs-util": true,
"ethereumjs-util>ethereum-cryptography": true,
"ethereumjs-wallet>aes-js": true,
"ethereumjs-wallet>bs58check": true,
"ethereumjs-wallet>randombytes": true,
"ethers>@ethersproject/json-wallets>scrypt-js": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>ethereumjs-wallet>uuid": {
"globals": {
"crypto": true,
"msCrypto": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>nanoid": {
"globals": {
"crypto.getRandomValues": true
@ -1597,7 +1773,7 @@
"setTimeout": true
}
},
"@metamask/snap-controllers": {
"@metamask/snaps-controllers": {
"globals": {
"URL": true,
"clearTimeout": true,
@ -1612,26 +1788,26 @@
"setTimeout": true
},
"packages": {
"@metamask/post-message-stream": true,
"@metamask/providers>@metamask/object-multiplex": true,
"@metamask/rpc-methods": true,
"@metamask/snap-controllers>@metamask/browser-passworder": true,
"@metamask/snap-controllers>@metamask/controllers": true,
"@metamask/snap-controllers>@metamask/post-message-stream": true,
"@metamask/snap-controllers>@xstate/fsm": true,
"@metamask/snap-controllers>concat-stream": true,
"@metamask/snap-controllers>gunzip-maybe": true,
"@metamask/snap-controllers>json-rpc-middleware-stream": true,
"@metamask/snap-controllers>nanoid": true,
"@metamask/snap-controllers>readable-web-to-node-stream": true,
"@metamask/snap-controllers>tar-stream": true,
"@metamask/snap-utils": true,
"@metamask/snaps-controllers>@metamask/browser-passworder": true,
"@metamask/snaps-controllers>@metamask/controllers": true,
"@metamask/snaps-controllers>@xstate/fsm": true,
"@metamask/snaps-controllers>concat-stream": true,
"@metamask/snaps-controllers>gunzip-maybe": true,
"@metamask/snaps-controllers>json-rpc-middleware-stream": true,
"@metamask/snaps-controllers>nanoid": true,
"@metamask/snaps-controllers>readable-web-to-node-stream": true,
"@metamask/snaps-controllers>tar-stream": true,
"@metamask/snaps-utils": true,
"eth-block-tracker>@metamask/utils": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"pump": true
}
},
"@metamask/snap-controllers>@metamask/browser-passworder": {
"@metamask/snaps-controllers>@metamask/browser-passworder": {
"globals": {
"btoa": true,
"crypto.getRandomValues": true,
@ -1644,7 +1820,7 @@
"browserify>buffer": true
}
},
"@metamask/snap-controllers>@metamask/controllers": {
"@metamask/snaps-controllers>@metamask/controllers": {
"globals": {
"Headers": true,
"URL": true,
@ -1672,9 +1848,9 @@
"@metamask/controllers>web3": true,
"@metamask/controllers>web3-provider-engine": true,
"@metamask/metamask-eth-abis": true,
"@metamask/snap-controllers>@metamask/controllers>eth-method-registry": true,
"@metamask/snap-controllers>@metamask/controllers>ethereumjs-wallet": true,
"@metamask/snap-controllers>nanoid": true,
"@metamask/snaps-controllers>@metamask/controllers>eth-method-registry": true,
"@metamask/snaps-controllers>@metamask/controllers>ethereumjs-wallet": true,
"@metamask/snaps-controllers>nanoid": true,
"browserify>buffer": true,
"browserify>events": true,
"deep-freeze-strict": true,
@ -1694,21 +1870,21 @@
"uuid": true
}
},
"@metamask/snap-controllers>@metamask/controllers>eth-method-registry": {
"@metamask/snaps-controllers>@metamask/controllers>eth-method-registry": {
"packages": {
"@metamask/snap-controllers>@metamask/controllers>eth-method-registry>ethjs": true
"@metamask/snaps-controllers>@metamask/controllers>eth-method-registry>ethjs": true
}
},
"@metamask/snap-controllers>@metamask/controllers>eth-method-registry>ethjs": {
"@metamask/snaps-controllers>@metamask/controllers>eth-method-registry>ethjs": {
"globals": {
"clearInterval": true,
"setInterval": true
},
"packages": {
"@metamask/snap-controllers>@metamask/controllers>eth-method-registry>ethjs>bn.js": true,
"@metamask/snap-controllers>@metamask/controllers>eth-method-registry>ethjs>ethjs-abi": true,
"@metamask/snap-controllers>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract": true,
"@metamask/snap-controllers>@metamask/controllers>eth-method-registry>ethjs>ethjs-query": true,
"@metamask/snaps-controllers>@metamask/controllers>eth-method-registry>ethjs>bn.js": true,
"@metamask/snaps-controllers>@metamask/controllers>eth-method-registry>ethjs>ethjs-abi": true,
"@metamask/snaps-controllers>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract": true,
"@metamask/snaps-controllers>@metamask/controllers>eth-method-registry>ethjs>ethjs-query": true,
"browserify>buffer": true,
"ethjs>ethjs-filter": true,
"ethjs>ethjs-provider-http": true,
@ -1718,17 +1894,17 @@
"ethjs>number-to-bn": true
}
},
"@metamask/snap-controllers>@metamask/controllers>eth-method-registry>ethjs>ethjs-abi": {
"@metamask/snaps-controllers>@metamask/controllers>eth-method-registry>ethjs>ethjs-abi": {
"packages": {
"@metamask/snap-controllers>@metamask/controllers>eth-method-registry>ethjs>bn.js": true,
"@metamask/snaps-controllers>@metamask/controllers>eth-method-registry>ethjs>bn.js": true,
"browserify>buffer": true,
"ethjs>js-sha3": true,
"ethjs>number-to-bn": true
}
},
"@metamask/snap-controllers>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract": {
"@metamask/snaps-controllers>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract": {
"packages": {
"@metamask/snap-controllers>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract>ethjs-abi": true,
"@metamask/snaps-controllers>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract>ethjs-abi": true,
"ethjs-query>babel-runtime": true,
"ethjs>ethjs-filter": true,
"ethjs>ethjs-util": true,
@ -1736,15 +1912,15 @@
"promise-to-callback": true
}
},
"@metamask/snap-controllers>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract>ethjs-abi": {
"@metamask/snaps-controllers>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract>ethjs-abi": {
"packages": {
"@metamask/snap-controllers>@metamask/controllers>eth-method-registry>ethjs>bn.js": true,
"@metamask/snaps-controllers>@metamask/controllers>eth-method-registry>ethjs>bn.js": true,
"browserify>buffer": true,
"ethjs>js-sha3": true,
"ethjs>number-to-bn": true
}
},
"@metamask/snap-controllers>@metamask/controllers>eth-method-registry>ethjs>ethjs-query": {
"@metamask/snaps-controllers>@metamask/controllers>eth-method-registry>ethjs>ethjs-query": {
"globals": {
"console": true
},
@ -1755,9 +1931,9 @@
"promise-to-callback": true
}
},
"@metamask/snap-controllers>@metamask/controllers>ethereumjs-wallet": {
"@metamask/snaps-controllers>@metamask/controllers>ethereumjs-wallet": {
"packages": {
"@metamask/snap-controllers>@metamask/controllers>ethereumjs-wallet>uuid": true,
"@metamask/snaps-controllers>@metamask/controllers>ethereumjs-wallet>uuid": true,
"@truffle/codec>utf8": true,
"browserify>buffer": true,
"browserify>crypto-browserify": true,
@ -1769,59 +1945,20 @@
"ethers>@ethersproject/json-wallets>scrypt-js": true
}
},
"@metamask/snap-controllers>@metamask/controllers>ethereumjs-wallet>uuid": {
"@metamask/snaps-controllers>@metamask/controllers>ethereumjs-wallet>uuid": {
"globals": {
"crypto": true,
"msCrypto": true
}
},
"@metamask/snap-controllers>@metamask/post-message-stream": {
"globals": {
"WorkerGlobalScope": true,
"addEventListener": true,
"location.origin": true,
"onmessage": "write",
"postMessage": true,
"removeEventListener": true
},
"packages": {
"@metamask/snap-controllers>@metamask/post-message-stream>@metamask/utils": true,
"@metamask/snap-controllers>@metamask/post-message-stream>readable-stream": true
}
},
"@metamask/snap-controllers>@metamask/post-message-stream>@metamask/utils": {
"packages": {
"eslint>fast-deep-equal": true
}
},
"@metamask/snap-controllers>@metamask/post-message-stream>readable-stream": {
"packages": {
"@metamask/snap-controllers>@metamask/post-message-stream>readable-stream>string_decoder": true,
"@metamask/snap-controllers>json-rpc-middleware-stream>readable-stream>safe-buffer": true,
"@storybook/api>util-deprecate": true,
"browserify>browser-resolve": true,
"browserify>events": true,
"browserify>process": true,
"browserify>timers-browserify": true,
"pumpify>inherits": true,
"readable-stream>core-util-is": true,
"readable-stream>isarray": true,
"vinyl>cloneable-readable>process-nextick-args": true
}
},
"@metamask/snap-controllers>@metamask/post-message-stream>readable-stream>string_decoder": {
"@metamask/snaps-controllers>concat-stream": {
"packages": {
"@metamask/snap-controllers>json-rpc-middleware-stream>readable-stream>safe-buffer": true
}
},
"@metamask/snap-controllers>concat-stream": {
"packages": {
"@metamask/snap-controllers>concat-stream>readable-stream": true,
"@metamask/snaps-controllers>concat-stream>readable-stream": true,
"browserify>buffer": true,
"pumpify>inherits": true
}
},
"@metamask/snap-controllers>concat-stream>readable-stream": {
"@metamask/snaps-controllers>concat-stream>readable-stream": {
"packages": {
"@storybook/api>util-deprecate": true,
"browserify>browser-resolve": true,
@ -1832,19 +1969,19 @@
"pumpify>inherits": true
}
},
"@metamask/snap-controllers>gunzip-maybe": {
"@metamask/snaps-controllers>gunzip-maybe": {
"packages": {
"@metamask/snap-controllers>gunzip-maybe>browserify-zlib": true,
"@metamask/snap-controllers>gunzip-maybe>is-deflate": true,
"@metamask/snap-controllers>gunzip-maybe>is-gzip": true,
"@metamask/snap-controllers>gunzip-maybe>peek-stream": true,
"@metamask/snap-controllers>gunzip-maybe>pumpify": true,
"@metamask/snap-controllers>gunzip-maybe>through2": true
"@metamask/snaps-controllers>gunzip-maybe>browserify-zlib": true,
"@metamask/snaps-controllers>gunzip-maybe>is-deflate": true,
"@metamask/snaps-controllers>gunzip-maybe>is-gzip": true,
"@metamask/snaps-controllers>gunzip-maybe>peek-stream": true,
"@metamask/snaps-controllers>gunzip-maybe>pumpify": true,
"@metamask/snaps-controllers>gunzip-maybe>through2": true
}
},
"@metamask/snap-controllers>gunzip-maybe>browserify-zlib": {
"@metamask/snaps-controllers>gunzip-maybe>browserify-zlib": {
"packages": {
"@metamask/snap-controllers>gunzip-maybe>browserify-zlib>pako": true,
"@metamask/snaps-controllers>gunzip-maybe>browserify-zlib>pako": true,
"browserify>assert": true,
"browserify>buffer": true,
"browserify>process": true,
@ -1852,15 +1989,15 @@
"readable-stream": true
}
},
"@metamask/snap-controllers>gunzip-maybe>peek-stream": {
"@metamask/snaps-controllers>gunzip-maybe>peek-stream": {
"packages": {
"@metamask/snap-controllers>gunzip-maybe>peek-stream>duplexify": true,
"@metamask/snap-controllers>gunzip-maybe>peek-stream>through2": true,
"@metamask/snaps-controllers>gunzip-maybe>peek-stream>duplexify": true,
"@metamask/snaps-controllers>gunzip-maybe>peek-stream>through2": true,
"browserify>buffer": true,
"terser>source-map-support>buffer-from": true
}
},
"@metamask/snap-controllers>gunzip-maybe>peek-stream>duplexify": {
"@metamask/snaps-controllers>gunzip-maybe>peek-stream>duplexify": {
"packages": {
"browserify>buffer": true,
"browserify>process": true,
@ -1870,7 +2007,7 @@
"readable-stream": true
}
},
"@metamask/snap-controllers>gunzip-maybe>peek-stream>through2": {
"@metamask/snaps-controllers>gunzip-maybe>peek-stream>through2": {
"packages": {
"browserify>process": true,
"browserify>util": true,
@ -1878,14 +2015,14 @@
"watchify>xtend": true
}
},
"@metamask/snap-controllers>gunzip-maybe>pumpify": {
"@metamask/snaps-controllers>gunzip-maybe>pumpify": {
"packages": {
"@metamask/snap-controllers>gunzip-maybe>pumpify>duplexify": true,
"@metamask/snap-controllers>gunzip-maybe>pumpify>pump": true,
"@metamask/snaps-controllers>gunzip-maybe>pumpify>duplexify": true,
"@metamask/snaps-controllers>gunzip-maybe>pumpify>pump": true,
"pumpify>inherits": true
}
},
"@metamask/snap-controllers>gunzip-maybe>pumpify>duplexify": {
"@metamask/snaps-controllers>gunzip-maybe>pumpify>duplexify": {
"packages": {
"browserify>buffer": true,
"browserify>process": true,
@ -1895,14 +2032,14 @@
"readable-stream": true
}
},
"@metamask/snap-controllers>gunzip-maybe>pumpify>pump": {
"@metamask/snaps-controllers>gunzip-maybe>pumpify>pump": {
"packages": {
"browserify>browser-resolve": true,
"end-of-stream": true,
"pump>once": true
}
},
"@metamask/snap-controllers>gunzip-maybe>through2": {
"@metamask/snaps-controllers>gunzip-maybe>through2": {
"packages": {
"browserify>process": true,
"browserify>util": true,
@ -1910,56 +2047,27 @@
"watchify>xtend": true
}
},
"@metamask/snap-controllers>json-rpc-middleware-stream": {
"@metamask/snaps-controllers>json-rpc-middleware-stream": {
"globals": {
"console.warn": true,
"setTimeout": true
},
"packages": {
"@metamask/snap-controllers>json-rpc-middleware-stream>readable-stream": true,
"json-rpc-engine>@metamask/safe-event-emitter": true
}
},
"@metamask/snap-controllers>json-rpc-middleware-stream>readable-stream": {
"packages": {
"@metamask/snap-controllers>json-rpc-middleware-stream>readable-stream>process-nextick-args": true,
"@metamask/snap-controllers>json-rpc-middleware-stream>readable-stream>safe-buffer": true,
"@metamask/snap-controllers>json-rpc-middleware-stream>readable-stream>string_decoder": true,
"@storybook/api>util-deprecate": true,
"browserify>browser-resolve": true,
"browserify>events": true,
"browserify>process": true,
"browserify>timers-browserify": true,
"pumpify>inherits": true,
"readable-stream>core-util-is": true,
"readable-stream>isarray": true
}
},
"@metamask/snap-controllers>json-rpc-middleware-stream>readable-stream>process-nextick-args": {
"packages": {
"browserify>process": true
}
},
"@metamask/snap-controllers>json-rpc-middleware-stream>readable-stream>safe-buffer": {
"packages": {
"browserify>buffer": true
}
},
"@metamask/snap-controllers>json-rpc-middleware-stream>readable-stream>string_decoder": {
"packages": {
"@metamask/snap-controllers>json-rpc-middleware-stream>readable-stream>safe-buffer": true
"json-rpc-engine>@metamask/safe-event-emitter": true,
"readable-stream": true
}
},
"@metamask/snap-controllers>nanoid": {
"@metamask/snaps-controllers>nanoid": {
"globals": {
"crypto.getRandomValues": true
}
},
"@metamask/snap-controllers>readable-web-to-node-stream": {
"@metamask/snaps-controllers>readable-web-to-node-stream": {
"packages": {
"@metamask/snap-controllers>readable-web-to-node-stream>readable-stream": true
"@metamask/snaps-controllers>readable-web-to-node-stream>readable-stream": true
}
},
"@metamask/snap-controllers>readable-web-to-node-stream>readable-stream": {
"@metamask/snaps-controllers>readable-web-to-node-stream>readable-stream": {
"packages": {
"@storybook/api>util-deprecate": true,
"browserify>browser-resolve": true,
@ -1970,10 +2078,10 @@
"pumpify>inherits": true
}
},
"@metamask/snap-controllers>tar-stream": {
"@metamask/snaps-controllers>tar-stream": {
"packages": {
"@metamask/snap-controllers>tar-stream>fs-constants": true,
"@metamask/snap-controllers>tar-stream>readable-stream": true,
"@metamask/snaps-controllers>tar-stream>fs-constants": true,
"@metamask/snaps-controllers>tar-stream>readable-stream": true,
"browserify>buffer": true,
"browserify>process": true,
"browserify>string_decoder": true,
@ -1983,12 +2091,12 @@
"pumpify>inherits": true
}
},
"@metamask/snap-controllers>tar-stream>fs-constants": {
"@metamask/snaps-controllers>tar-stream>fs-constants": {
"packages": {
"browserify>constants-browserify": true
}
},
"@metamask/snap-controllers>tar-stream>readable-stream": {
"@metamask/snaps-controllers>tar-stream>readable-stream": {
"packages": {
"@storybook/api>util-deprecate": true,
"browserify>browser-resolve": true,
@ -1999,18 +2107,18 @@
"pumpify>inherits": true
}
},
"@metamask/snap-utils": {
"@metamask/snaps-utils": {
"globals": {
"URL": true
},
"packages": {
"@babel/core": true,
"@babel/core>@babel/types": true,
"@metamask/snap-utils>@noble/hashes": true,
"@metamask/snap-utils>@scure/base": true,
"@metamask/snap-utils>ajv": true,
"@metamask/snap-utils>rfdc": true,
"@metamask/snap-utils>superstruct": true,
"@metamask/snaps-utils>@noble/hashes": true,
"@metamask/snaps-utils>@scure/base": true,
"@metamask/snaps-utils>cron-parser": true,
"@metamask/snaps-utils>rfdc": true,
"@metamask/snaps-utils>superstruct": true,
"browserify": true,
"browserify>buffer": true,
"browserify>crypto-browserify": true,
@ -2021,19 +2129,25 @@
"semver": true
}
},
"@metamask/snap-utils>@noble/hashes": {
"@metamask/snaps-utils>@noble/hashes": {
"globals": {
"TextEncoder": true,
"crypto": true
}
},
"@metamask/snap-utils>@scure/base": {
"@metamask/snaps-utils>@scure/base": {
"globals": {
"TextDecoder": true,
"TextEncoder": true
}
},
"@metamask/snap-utils>rfdc": {
"@metamask/snaps-utils>cron-parser": {
"packages": {
"browserify>browser-resolve": true,
"luxon": true
}
},
"@metamask/snaps-utils>rfdc": {
"packages": {
"browserify>buffer": true
}
@ -3025,7 +3139,7 @@
"TextEncoder": true
},
"packages": {
"@metamask/snap-utils>superstruct": true,
"@metamask/snaps-utils>superstruct": true,
"browserify>buffer": true,
"nock>debug": true
}

@ -1238,14 +1238,145 @@
"packages": {
"@ethersproject/bignumber": true,
"@ethersproject/bignumber>@ethersproject/bytes": true,
"@metamask/controllers": true,
"@metamask/controllers>@ethersproject/providers": true,
"@metamask/controllers>isomorphic-fetch": true,
"@metamask/smart-transactions-controller>@metamask/controllers": true,
"@metamask/smart-transactions-controller>bignumber.js": true,
"@metamask/smart-transactions-controller>fast-json-patch": true,
"lodash": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers": {
"globals": {
"Headers": true,
"URL": true,
"clearInterval": true,
"clearTimeout": true,
"console.error": true,
"console.log": true,
"fetch": true,
"setInterval": true,
"setTimeout": true
},
"packages": {
"@ethereumjs/common": true,
"@ethereumjs/tx": true,
"@metamask/contract-metadata": true,
"@metamask/controllers>@ethersproject/abi": true,
"@metamask/controllers>@ethersproject/contracts": true,
"@metamask/controllers>@ethersproject/providers": true,
"@metamask/controllers>abort-controller": true,
"@metamask/controllers>async-mutex": true,
"@metamask/controllers>eth-json-rpc-infura": true,
"@metamask/controllers>eth-phishing-detect": true,
"@metamask/controllers>isomorphic-fetch": true,
"@metamask/controllers>multiformats": true,
"@metamask/controllers>web3": true,
"@metamask/controllers>web3-provider-engine": true,
"@metamask/metamask-eth-abis": true,
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry": true,
"@metamask/smart-transactions-controller>@metamask/controllers>ethereumjs-wallet": true,
"@metamask/smart-transactions-controller>@metamask/controllers>nanoid": true,
"browserify>buffer": true,
"browserify>events": true,
"deep-freeze-strict": true,
"eslint>fast-deep-equal": true,
"eth-ens-namehash": true,
"eth-keyring-controller": true,
"eth-query": true,
"eth-rpc-errors": true,
"eth-sig-util": true,
"ethereumjs-util": true,
"ethjs>ethjs-unit": true,
"immer": true,
"json-rpc-engine": true,
"jsonschema": true,
"punycode": true,
"single-call-balance-checker-abi": true,
"uuid": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry": {
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs": {
"globals": {
"clearInterval": true,
"setInterval": true
},
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>bn.js": true,
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-abi": true,
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract": true,
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-query": true,
"browserify>buffer": true,
"ethjs>ethjs-filter": true,
"ethjs>ethjs-provider-http": true,
"ethjs>ethjs-unit": true,
"ethjs>ethjs-util": true,
"ethjs>js-sha3": true,
"ethjs>number-to-bn": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-abi": {
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>bn.js": true,
"browserify>buffer": true,
"ethjs>js-sha3": true,
"ethjs>number-to-bn": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract": {
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract>ethjs-abi": true,
"ethjs-query>babel-runtime": true,
"ethjs>ethjs-filter": true,
"ethjs>ethjs-util": true,
"ethjs>js-sha3": true,
"promise-to-callback": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-contract>ethjs-abi": {
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>bn.js": true,
"browserify>buffer": true,
"ethjs>js-sha3": true,
"ethjs>number-to-bn": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>eth-method-registry>ethjs>ethjs-query": {
"globals": {
"console": true
},
"packages": {
"ethjs-query>babel-runtime": true,
"ethjs-query>ethjs-format": true,
"ethjs-query>ethjs-rpc": true,
"promise-to-callback": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>ethereumjs-wallet": {
"packages": {
"@metamask/smart-transactions-controller>@metamask/controllers>ethereumjs-wallet>uuid": true,
"@truffle/codec>utf8": true,
"browserify>buffer": true,
"browserify>crypto-browserify": true,
"ethereumjs-util": true,
"ethereumjs-util>ethereum-cryptography": true,
"ethereumjs-wallet>aes-js": true,
"ethereumjs-wallet>bs58check": true,
"ethereumjs-wallet>randombytes": true,
"ethers>@ethersproject/json-wallets>scrypt-js": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>ethereumjs-wallet>uuid": {
"globals": {
"crypto": true,
"msCrypto": true
}
},
"@metamask/smart-transactions-controller>@metamask/controllers>nanoid": {
"globals": {
"crypto.getRandomValues": true
@ -1265,7 +1396,7 @@
"setTimeout": true
}
},
"@metamask/snap-controllers>nanoid": {
"@metamask/snaps-controllers>nanoid": {
"globals": {
"crypto.getRandomValues": true
}
@ -2223,7 +2354,7 @@
"TextEncoder": true
},
"packages": {
"@metamask/snap-utils>superstruct": true,
"@metamask/snaps-utils>superstruct": true,
"browserify>buffer": true,
"nock>debug": true
}

@ -87,7 +87,7 @@
"crypto.getRandomValues": true
}
},
"@metamask/snap-controllers>nanoid": {
"@metamask/snaps-controllers>nanoid": {
"globals": {
"crypto.getRandomValues": true
}

@ -1187,6 +1187,16 @@
"typescript": true
}
},
"addons-linter>postcss>picocolors": {
"builtin": {
"tty.isatty": true
},
"globals": {
"process.argv.includes": true,
"process.env": true,
"process.platform": true
}
},
"babelify": {
"builtin": {
"path.extname": true,
@ -3206,106 +3216,58 @@
"fancy-log": true,
"gulp-autoprefixer>autoprefixer": true,
"gulp-autoprefixer>postcss": true,
"gulp-autoprefixer>through2": true,
"gulp-zip>plugin-error": true,
"through2": true,
"vinyl-sourcemaps-apply": true
}
},
"gulp-autoprefixer>autoprefixer": {
"globals": {
"process.cwd": true
"console": true,
"process.cwd": true,
"process.env.AUTOPREFIXER_GRID": true
},
"packages": {
"gulp-autoprefixer>autoprefixer>browserslist": true,
"gulp-autoprefixer>autoprefixer>postcss-value-parser": true,
"addons-linter>postcss>picocolors": true,
"gulp-autoprefixer>autoprefixer>fraction.js": true,
"gulp-autoprefixer>postcss": true,
"stylelint>autoprefixer>browserslist": true,
"stylelint>autoprefixer>caniuse-lite": true,
"stylelint>autoprefixer>normalize-range": true,
"stylelint>autoprefixer>num2fraction": true
"stylelint>postcss-value-parser": true
}
},
"gulp-autoprefixer>autoprefixer>browserslist": {
"builtin": {
"fs.existsSync": true,
"fs.readFileSync": true,
"fs.statSync": true,
"path.basename": true,
"path.dirname": true,
"path.join": true,
"path.resolve": true
},
"gulp-autoprefixer>autoprefixer>fraction.js": {
"globals": {
"console.warn": true,
"process.env.BROWSERSLIST": true,
"process.env.BROWSERSLIST_CONFIG": true,
"process.env.BROWSERSLIST_DISABLE_CACHE": true,
"process.env.BROWSERSLIST_ENV": true,
"process.env.BROWSERSLIST_STATS": true,
"process.env.NODE_ENV": true
},
"packages": {
"stylelint>autoprefixer>browserslist>electron-to-chromium": true,
"stylelint>autoprefixer>caniuse-lite": true
"define": true
}
},
"gulp-autoprefixer>postcss": {
"builtin": {
"fs": true,
"path": true
"fs.existsSync": true,
"fs.readFileSync": true,
"path.dirname": true,
"path.isAbsolute": true,
"path.join": true,
"path.relative": true,
"path.resolve": true,
"path.sep": true,
"url.fileURLToPath": true,
"url.pathToFileURL": true
},
"globals": {
"Buffer": true,
"URL": true,
"atob": true,
"btoa": true,
"console": true
},
"packages": {
"gulp-autoprefixer>postcss>chalk": true,
"gulp-autoprefixer>postcss>source-map": true,
"gulp-autoprefixer>postcss>supports-color": true
}
},
"gulp-autoprefixer>postcss>chalk": {
"globals": {
"process.env.TERM": true,
"process.platform": true
},
"packages": {
"gulp-autoprefixer>postcss>chalk>ansi-styles": true,
"gulp-autoprefixer>postcss>supports-color": true,
"mocha>escape-string-regexp": true
}
},
"gulp-autoprefixer>postcss>chalk>ansi-styles": {
"packages": {
"@metamask/jazzicon>color>color-convert": true
}
},
"gulp-autoprefixer>postcss>supports-color": {
"builtin": {
"os.release": true
},
"globals": {
"process.env": true,
"process.platform": true,
"process.stderr": true,
"process.stdout": true,
"process.versions.node.split": true
},
"packages": {
"mocha>supports-color>has-flag": true
}
},
"gulp-autoprefixer>through2": {
"builtin": {
"util.inherits": true
},
"globals": {
"process.nextTick": true
"console": true,
"process.env.LANG": true,
"process.env.NODE_ENV": true
},
"packages": {
"readable-stream": true,
"watchify>xtend": true
"addons-linter>postcss>picocolors": true,
"addons-linter>postcss>source-map-js": true,
"gulp-autoprefixer>postcss>nanoid": true
}
},
"gulp-dart-sass": {
@ -6453,9 +6415,9 @@
"stylelint>autoprefixer>caniuse-lite": true,
"stylelint>autoprefixer>normalize-range": true,
"stylelint>autoprefixer>num2fraction": true,
"stylelint>autoprefixer>picocolors": true,
"stylelint>postcss": true,
"stylelint>postcss-value-parser": true,
"stylelint>postcss>picocolors": true
"stylelint>postcss-value-parser": true
}
},
"stylelint>autoprefixer>browserslist": {
@ -6486,6 +6448,16 @@
"stylelint>autoprefixer>caniuse-lite": true
}
},
"stylelint>autoprefixer>picocolors": {
"builtin": {
"tty.isatty": true
},
"globals": {
"process.argv.includes": true,
"process.env": true,
"process.platform": true
}
},
"stylelint>chalk": {
"packages": {
"chalk>ansi-styles": true,

@ -65,7 +65,6 @@
"start:dev": "concurrently -k -n build,react,redux yarn:start yarn:devtools:react yarn:devtools:redux",
"announce": "node development/announcer.js",
"storybook": "start-storybook -p 6006 -c .storybook",
"storybook:test": "jest --config=./jest.stories.config.js",
"storybook:build": "build-storybook -c .storybook -o storybook-build",
"storybook:deploy": "storybook-to-ghpages --existing-output-dir storybook-build --remote storybook --branch master",
"update-changelog": "auto-changelog update",
@ -84,7 +83,6 @@
},
"resolutions": {
"**/regenerator-runtime": "^0.13.7",
"**/caniuse-lite": "^1.0.30001312",
"**/cross-fetch": "^3.1.5",
"**/configstore/dot-prop": "^5.1.1",
"**/ethers/elliptic": "^6.5.4",
@ -112,7 +110,7 @@
"@keystonehq/metamask-airgapped-keyring": "^0.6.1",
"@material-ui/core": "^4.11.0",
"@metamask/contract-metadata": "^1.31.0",
"@metamask/controllers": "^32.0.2",
"@metamask/controllers": "^33.0.0",
"@metamask/design-tokens": "^1.9.0",
"@metamask/eth-json-rpc-infura": "^7.0.0",
"@metamask/eth-ledger-bridge-keyring": "^0.13.0",
@ -122,13 +120,13 @@
"@metamask/logo": "^3.1.1",
"@metamask/metamask-eth-abis": "^3.0.0",
"@metamask/obs-store": "^5.0.0",
"@metamask/post-message-stream": "^4.0.0",
"@metamask/post-message-stream": "^6.0.0",
"@metamask/providers": "^10.0.0",
"@metamask/rpc-methods": "^0.22.2",
"@metamask/rpc-methods": "^0.24.1",
"@metamask/slip44": "^2.1.0",
"@metamask/smart-transactions-controller": "^3.0.0",
"@metamask/snap-controllers": "^0.22.2",
"@metamask/snap-utils": "^0.22.2",
"@metamask/snaps-controllers": "^0.24.1",
"@metamask/snaps-utils": "^0.24.1",
"@ngraveio/bc-ur": "^1.1.6",
"@popperjs/core": "^2.4.0",
"@reduxjs/toolkit": "^1.6.2",
@ -187,7 +185,7 @@
"localforage": "^1.9.0",
"lodash": "^4.17.21",
"loglevel": "^1.4.1",
"luxon": "^1.26.0",
"luxon": "^3.1.0",
"nanoid": "^2.1.6",
"nonce-tracker": "^1.0.0",
"obj-multiplex": "^1.0.0",
@ -325,11 +323,11 @@
"fast-glob": "^3.2.2",
"fs-extra": "^8.1.0",
"ganache": "^v7.0.4",
"geckodriver": "^1.21.0",
"geckodriver": "^3.2.0",
"gh-pages": "^3.2.3",
"globby": "^11.0.4",
"gulp": "^4.0.2",
"gulp-autoprefixer": "^5.0.0",
"gulp-autoprefixer": "^8.0.0",
"gulp-dart-sass": "^1.0.2",
"gulp-livereload": "4.0.0",
"gulp-rename": "^2.0.0",

@ -1,20 +1,22 @@
diff --git a/node_modules/luxon/build/cjs-browser/luxon.js b/node_modules/luxon/build/cjs-browser/luxon.js
index 206a47a..5556d1d 100644
index 9ab2b9f..14c2891 100644
--- a/node_modules/luxon/build/cjs-browser/luxon.js
+++ b/node_modules/luxon/build/cjs-browser/luxon.js
@@ -7243,13 +7243,13 @@ var DateTime = /*#__PURE__*/function () {
@@ -7373,7 +7373,7 @@ var DateTime = /*#__PURE__*/function () {
*/
;
- _proto.toLocaleString = function toLocaleString(opts) {
+ Reflect.defineProperty(_proto, 'toLocaleString', { value: function toLocaleString(opts) {
if (opts === void 0) {
opts = DATE_SHORT;
- _proto.toLocaleString = function toLocaleString(formatOpts, opts) {
+ Reflect.defineProperty(_proto, 'toLocaleString', { value: function toLocaleString(formatOpts, opts) {
if (formatOpts === void 0) {
formatOpts = DATE_SHORT;
}
@@ -7383,7 +7383,7 @@ var DateTime = /*#__PURE__*/function () {
}
return this.isValid ? Formatter.create(this.loc.clone(opts), opts).formatDateTime(this) : INVALID$2;
return this.isValid ? Formatter.create(this.loc.clone(opts), formatOpts).formatDateTime(this) : INVALID;
- }
+ }})
/**
* Returns an array of format "parts", meaning individual tokens along with metadata. This is allows callers to post-process individual sections of the formatted output.
* Defaults to the system's locale if no locale has been specified
* Defaults to the system's locale if no locale has been specified

@ -57,6 +57,14 @@ export const MESSAGE_TYPE = {
///: END:ONLY_INCLUDE_IN
} as const;
/**
* Custom messages to send and be received by the extension
*/
export const EXTENSION_MESSAGES = {
CONNECTION_READY: 'CONNECTION_READY',
READY: 'METAMASK_EXTENSION_READY',
} as const;
/**
* The different kinds of subjects that MetaMask may interact with, including
* third parties and itself (e.g. when the background communicated with the UI).

@ -3,12 +3,11 @@
* keyring types. Both simple and HD are treated as default but we do special
* case accounts managed by a hardware wallet.
*/
export const KEYRING_TYPES = {
export const HARDWARE_KEYRING_TYPES = {
LEDGER: 'Ledger Hardware',
TREZOR: 'Trezor Hardware',
LATTICE: 'Lattice Hardware',
QR: 'QR Hardware Wallet Device',
IMPORTED: 'Simple Key Pair',
};
export const DEVICE_NAMES = {

@ -0,0 +1,7 @@
import { HARDWARE_KEYRING_TYPES } from './hardware-wallets';
export const KEYRING_TYPES = {
HD_KEY_TREE: 'HD Key Tree',
IMPORTED: 'Simple Key Pair',
...HARDWARE_KEYRING_TYPES,
};

@ -274,6 +274,7 @@ export const CURRENCY_SYMBOLS = {
USDC: 'USDC',
USDT: 'USDT',
WETH: 'WETH',
OPTIMISM: 'OP',
} as const;
/**
@ -531,6 +532,7 @@ export const NATIVE_CURRENCY_TOKEN_IMAGE_MAP = {
[CURRENCY_SYMBOLS.BNB]: BNB_TOKEN_IMAGE_URL,
[CURRENCY_SYMBOLS.MATIC]: MATIC_TOKEN_IMAGE_URL,
[CURRENCY_SYMBOLS.AVALANCHE]: AVAX_TOKEN_IMAGE_URL,
[CURRENCY_SYMBOLS.OPTIMISM]: OPTIMISM_TOKEN_IMAGE_URL,
} as const;
export const INFURA_BLOCKED_KEY = 'countryBlocked';

@ -1,4 +1,4 @@
import { endowmentPermissionBuilders } from '@metamask/snap-controllers';
import { endowmentPermissionBuilders } from '@metamask/snaps-controllers';
import { restrictedMethodPermissionBuilders } from '@metamask/rpc-methods';
import {
EndowmentPermissions,

@ -11,6 +11,7 @@ export const RestrictedMethods = Object.freeze({
snap_getBip32PublicKey: 'snap_getBip32PublicKey',
snap_getBip32Entropy: 'snap_getBip32Entropy',
snap_getBip44Entropy: 'snap_getBip44Entropy',
snap_getEntropy: 'snap_getEntropy',
'wallet_snap_*': 'wallet_snap_*',
///: END:ONLY_INCLUDE_IN
} as const);
@ -24,6 +25,8 @@ export const EndowmentPermissions = Object.freeze({
'endowment:network-access': 'endowment:network-access',
'endowment:long-running': 'endowment:long-running',
'endowment:transaction-insight': 'endowment:transaction-insight',
'endowment:cronjob': 'endowment:cronjob',
'endowment:ethereum-provider': 'endowment:ethereum-provider',
} as const);
// Methods / permissions in external packages that we are temporarily excluding.

@ -1,4 +1,5 @@
import {
ETH_TOKEN_IMAGE_URL,
TEST_ETH_TOKEN_IMAGE_URL,
BNB_TOKEN_IMAGE_URL,
MATIC_TOKEN_IMAGE_URL,
@ -23,7 +24,7 @@ export const ETH_SWAPS_TOKEN_OBJECT = {
name: 'Ether',
address: DEFAULT_TOKEN_ADDRESS,
decimals: 18,
iconUrl: './images/black-eth-logo.svg',
iconUrl: ETH_TOKEN_IMAGE_URL,
};
export const BNB_SWAPS_TOKEN_OBJECT = {
@ -66,6 +67,10 @@ export const GOERLI_SWAPS_TOKEN_OBJECT = {
iconUrl: TEST_ETH_TOKEN_IMAGE_URL,
};
export const ARBITRUM_SWAPS_TOKEN_OBJECT = { ...ETH_SWAPS_TOKEN_OBJECT };
export const OPTIMISM_SWAPS_TOKEN_OBJECT = { ...ETH_SWAPS_TOKEN_OBJECT };
// A gas value for ERC20 approve calls that should be sufficient for all ERC20 approve implementations
export const DEFAULT_ERC20_APPROVE_GAS = '0x1d4c0';
@ -77,8 +82,9 @@ const BSC_CONTRACT_ADDRESS = '0x1a1ec25dc08e98e5e93f1104b5e5cdd298707d31';
// It's the same as we use for BSC.
const POLYGON_CONTRACT_ADDRESS = '0x1a1ec25dc08e98e5e93f1104b5e5cdd298707d31';
const AVALANCHE_CONTRACT_ADDRESS = '0x1a1ec25dc08e98e5e93f1104b5e5cdd298707d31';
const OPTIMISM_CONTRACT_ADDRESS = '0x9dDA6Ef3D919c9bC8885D5560999A3640431e8e6';
const ARBITRUM_CONTRACT_ADDRESS = '0x9dDA6Ef3D919c9bC8885D5560999A3640431e8e6';
export const WETH_CONTRACT_ADDRESS =
'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
@ -91,6 +97,11 @@ export const WMATIC_CONTRACT_ADDRESS =
export const WAVAX_CONTRACT_ADDRESS =
'0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7';
export const WETH_OPTIMISM_CONTRACT_ADDRESS =
'0x4200000000000000000000000000000000000006';
export const WETH_ARBITRUM_CONTRACT_ADDRESS =
'0x82aF49447D8a07e3bd95BD0d56f35241523fBab1';
const SWAPS_TESTNET_CHAIN_ID = '0x539';
export const SWAPS_API_V2_BASE_URL = 'https://swap.metaswap.codefi.network';
@ -105,6 +116,8 @@ const MAINNET_DEFAULT_BLOCK_EXPLORER_URL = 'https://etherscan.io/';
const GOERLI_DEFAULT_BLOCK_EXPLORER_URL = 'https://goerli.etherscan.io/';
const POLYGON_DEFAULT_BLOCK_EXPLORER_URL = 'https://polygonscan.com/';
const AVALANCHE_DEFAULT_BLOCK_EXPLORER_URL = 'https://snowtrace.io/';
const OPTIMISM_DEFAULT_BLOCK_EXPLORER_URL = 'https://optimistic.etherscan.io/';
const ARBITRUM_DEFAULT_BLOCK_EXPLORER_URL = 'https://arbiscan.io/';
export const ALLOWED_PROD_SWAPS_CHAIN_IDS = [
CHAIN_IDS.MAINNET,
@ -112,6 +125,8 @@ export const ALLOWED_PROD_SWAPS_CHAIN_IDS = [
CHAIN_IDS.BSC,
CHAIN_IDS.POLYGON,
CHAIN_IDS.AVALANCHE,
CHAIN_IDS.OPTIMISM,
CHAIN_IDS.ARBITRUM,
];
export const ALLOWED_DEV_SWAPS_CHAIN_IDS = [
@ -131,6 +146,8 @@ export const SWAPS_CHAINID_CONTRACT_ADDRESS_MAP = {
[CHAIN_IDS.POLYGON]: POLYGON_CONTRACT_ADDRESS,
[CHAIN_IDS.GOERLI]: TESTNET_CONTRACT_ADDRESS,
[CHAIN_IDS.AVALANCHE]: AVALANCHE_CONTRACT_ADDRESS,
[CHAIN_IDS.OPTIMISM]: OPTIMISM_CONTRACT_ADDRESS,
[CHAIN_IDS.ARBITRUM]: ARBITRUM_CONTRACT_ADDRESS,
};
export const SWAPS_WRAPPED_TOKENS_ADDRESSES = {
@ -140,6 +157,8 @@ export const SWAPS_WRAPPED_TOKENS_ADDRESSES = {
[CHAIN_IDS.POLYGON]: WMATIC_CONTRACT_ADDRESS,
[CHAIN_IDS.GOERLI]: WETH_GOERLI_CONTRACT_ADDRESS,
[CHAIN_IDS.AVALANCHE]: WAVAX_CONTRACT_ADDRESS,
[CHAIN_IDS.OPTIMISM]: WETH_OPTIMISM_CONTRACT_ADDRESS,
[CHAIN_IDS.ARBITRUM]: WETH_ARBITRUM_CONTRACT_ADDRESS,
};
export const ALLOWED_CONTRACT_ADDRESSES = {
@ -167,6 +186,14 @@ export const ALLOWED_CONTRACT_ADDRESSES = {
SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[CHAIN_IDS.AVALANCHE],
SWAPS_WRAPPED_TOKENS_ADDRESSES[CHAIN_IDS.AVALANCHE],
],
[CHAIN_IDS.OPTIMISM]: [
SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[CHAIN_IDS.OPTIMISM],
SWAPS_WRAPPED_TOKENS_ADDRESSES[CHAIN_IDS.OPTIMISM],
],
[CHAIN_IDS.ARBITRUM]: [
SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[CHAIN_IDS.ARBITRUM],
SWAPS_WRAPPED_TOKENS_ADDRESSES[CHAIN_IDS.ARBITRUM],
],
};
export const SWAPS_CHAINID_DEFAULT_TOKEN_MAP = {
@ -176,6 +203,8 @@ export const SWAPS_CHAINID_DEFAULT_TOKEN_MAP = {
[CHAIN_IDS.POLYGON]: MATIC_SWAPS_TOKEN_OBJECT,
[CHAIN_IDS.GOERLI]: GOERLI_SWAPS_TOKEN_OBJECT,
[CHAIN_IDS.AVALANCHE]: AVAX_SWAPS_TOKEN_OBJECT,
[CHAIN_IDS.OPTIMISM]: OPTIMISM_SWAPS_TOKEN_OBJECT,
[CHAIN_IDS.ARBITRUM]: ARBITRUM_SWAPS_TOKEN_OBJECT,
};
export const SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP = {
@ -184,6 +213,8 @@ export const SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP = {
[CHAIN_IDS.POLYGON]: POLYGON_DEFAULT_BLOCK_EXPLORER_URL,
[CHAIN_IDS.GOERLI]: GOERLI_DEFAULT_BLOCK_EXPLORER_URL,
[CHAIN_IDS.AVALANCHE]: AVALANCHE_DEFAULT_BLOCK_EXPLORER_URL,
[CHAIN_IDS.OPTIMISM]: OPTIMISM_DEFAULT_BLOCK_EXPLORER_URL,
[CHAIN_IDS.ARBITRUM]: ARBITRUM_DEFAULT_BLOCK_EXPLORER_URL,
};
export const ETHEREUM = 'ethereum';
@ -191,6 +222,8 @@ export const POLYGON = 'polygon';
export const BSC = 'bsc';
export const GOERLI = 'goerli';
export const AVALANCHE = 'avalanche';
export const OPTIMISM = 'optimism';
export const ARBITRUM = 'arbitrum';
export const SWAPS_CLIENT_ID = 'extension';

@ -12,11 +12,11 @@ export const LISTED_CONTRACT_ADDRESSES = Object.keys(contractMap).map(
/**
* @typedef {object} TokenDetails
* @property {string} address - The address of the selected 'TOKEN' or
* 'COLLECTIBLE' contract.
* 'NFT' contract.
* @property {string} [symbol] - The symbol of the token.
* @property {number} [decimals] - The number of decimals of the selected
* 'ERC20' asset.
* @property {number} [tokenId] - The id of the selected 'COLLECTIBLE' asset.
* @property {number} [tokenId] - The id of the selected 'NFT' asset.
* @property {TokenStandardStrings} [standard] - The standard of the selected
* asset.
* @property {boolean} [isERC721] - True when the asset is a ERC721 token.

@ -364,7 +364,7 @@ export const TRANSACTION_EVENTS = {
* @property {'NATIVE'} NATIVE - The native asset for the current network, such
* as ETH
* @property {'TOKEN'} TOKEN - An ERC20 token.
* @property {'COLLECTIBLE'} COLLECTIBLE - An ERC721 or ERC1155 token.
* @property {'NFT'} NFT - An ERC721 or ERC1155 token.
* @property {'UNKNOWN'} UNKNOWN - A transaction interacting with a contract
* that isn't a token method interaction will be marked as dealing with an
* unknown asset type.
@ -385,7 +385,7 @@ export const TRANSACTION_EVENTS = {
export const ASSET_TYPES = {
NATIVE: 'NATIVE',
TOKEN: 'TOKEN',
COLLECTIBLE: 'COLLECTIBLE',
NFT: 'NFT',
UNKNOWN: 'UNKNOWN',
};

@ -32,7 +32,7 @@ const getLocaleContext = (currentLocaleMessages, enLocaleMessages) => {
};
};
export async function getErrorHtml(supportLink, metamaskState) {
export async function getErrorHtml(errorKey, supportLink, metamaskState) {
let response, preferredLocale;
if (metamaskState?.currentLocale) {
preferredLocale = metamaskState.currentLocale;
@ -50,26 +50,40 @@ export async function getErrorHtml(supportLink, metamaskState) {
const { currentLocaleMessages, enLocaleMessages } = response;
const t = getLocaleContext(currentLocaleMessages, enLocaleMessages);
/**
* The pattern ${errorKey === 'troubleStarting' ? t('troubleStarting') : ''}
* is neccessary because we we need linter to see the string
* of the locale keys. If we use the variable directly, the linter will not
* see the string and will not be able to check if the locale key exists.
*/
return `
<div class="critical-error">
<div class="critical-error__alert">
<p class="critical-error__alert__message">
${t('troubleStarting')}
<div class="critical-error__icon">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.2325 9.78823L9.14559 1.96347C8.59641 0.910661 7.83651 0.333313 6.99998 0.333313C6.16345 0.333313 5.40354 0.910661 4.85437 1.96347L0.767492 9.78823C0.250247 10.7867 0.192775 11.7444 0.607848 12.4984C1.02292 13.2523 1.8403 13.6666 2.9131 13.6666H11.0869C12.1597 13.6666 12.977 13.2523 13.3921 12.4984C13.8072 11.7444 13.7497 10.7799 13.2325 9.78823ZM6.52105 5.08794C6.52105 4.80945 6.73816 4.57852 6.99998 4.57852C7.26179 4.57852 7.47891 4.80945 7.47891 5.08794V8.4841C7.47891 8.76259 7.26179 8.99353 6.99998 8.99353C6.73816 8.99353 6.52105 8.76259 6.52105 8.4841V5.08794ZM7.45337 11.0041C7.42144 11.0312 7.38951 11.0584 7.35758 11.0856C7.31927 11.1127 7.28095 11.1331 7.24264 11.1467C7.20432 11.1671 7.16601 11.1807 7.12131 11.1874C7.08299 11.1942 7.03829 11.201 6.99998 11.201C6.96166 11.201 6.91696 11.1942 6.87226 11.1874C6.83395 11.1807 6.79563 11.1671 6.75732 11.1467C6.71901 11.1331 6.68069 11.1127 6.64238 11.0856C6.61045 11.0584 6.57852 11.0312 6.54659 11.0041C6.43165 10.875 6.3614 10.6984 6.3614 10.5218C6.3614 10.3452 6.43165 10.1686 6.54659 10.0395C6.57852 10.0124 6.61045 9.98521 6.64238 9.95804C6.68069 9.93087 6.71901 9.91049 6.75732 9.8969C6.79563 9.87653 6.83395 9.86294 6.87226 9.85615C6.95528 9.83577 7.04468 9.83577 7.12131 9.85615C7.16601 9.86294 7.20432 9.87653 7.24264 9.8969C7.28095 9.91049 7.31927 9.93087 7.35758 9.95804C7.38951 9.98521 7.42144 10.0124 7.45337 10.0395C7.56831 10.1686 7.63855 10.3452 7.63855 10.5218C7.63855 10.6984 7.56831 10.875 7.45337 11.0041Z" fill="#F66A0A"/>
</svg>
</div>
<div class="critical-error__dscription">
<div class="critical-error__alert">
<p class="critical-error__alert__message">
${errorKey === 'troubleStarting' ? t('troubleStarting') : ''}
${errorKey === 'somethingIsWrong' ? t('somethingIsWrong') : ''}
</p>
<span id='critical-error-button' class="critical-error__alert__action-link">
${t('restartMetamask')}
</span>
</div>
<p class="critical-error__paragraph">
${t('stillGettingMessage')}
<a
href=${supportLink}
class="critical-error__paragraph__link"
target="_blank"
rel="noopener noreferrer">
${t('sendBugReport')}
</a>
</p>
<button id='critical-error-button' class="critical-error__alert__button">
${t('restartMetamask')}
</button>
</div>
<p class="critical-error__paragraph">
${t('stillGettingMessage')}
<a
href=${supportLink}
class="critical-error__paragraph__link"
target="_blank"
rel="noopener noreferrer">
${t('sendBugReport')}
</a>
</p>
</div>
`;
}

@ -33,7 +33,11 @@ describe('Error utils Tests', function () {
};
fetchLocale.mockReturnValue(mockStore.localeMessages.current);
const errorHtml = await getErrorHtml(SUPPORT_LINK, mockStore.metamask);
const errorHtml = await getErrorHtml(
'troubleStarting',
SUPPORT_LINK,
mockStore.metamask,
);
const currentLocale = mockStore.localeMessages.current;
const troubleStartingMessage = currentLocale.troubleStarting.message;
const restartMetamaskMessage = currentLocale.restartMetamask.message;

@ -146,7 +146,6 @@ export const getBaseApi = function (type, chainId = CHAIN_IDS.MAINNET) {
// eslint-disable-next-line no-param-reassign
chainId = TEST_CHAIN_IDS.includes(chainId) ? CHAIN_IDS.MAINNET : chainId;
const baseUrl = getBaseUrlForNewSwapsApi(type, chainId);
const chainIdDecimal = chainId && parseInt(chainId, 16);
if (!baseUrl) {
throw new Error(`Swaps API calls are disabled for chainId: ${chainId}`);
}
@ -164,8 +163,7 @@ export const getBaseApi = function (type, chainId = CHAIN_IDS.MAINNET) {
case 'gasPrices':
return `${baseUrl}/gasPrices`;
case 'network':
// Only use v2 for this endpoint.
return `${SWAPS_API_V2_BASE_URL}/networks/${chainIdDecimal}`;
return baseUrl;
default:
throw new Error('getBaseApi requires an api call type');
}

@ -0,0 +1,55 @@
/**
* Utility Functions to support browser.runtime JavaScript API
*/
import browser from 'webextension-polyfill';
import log from 'loglevel';
/**
* Returns an Error if extension.runtime.lastError is present
* this is a workaround for the non-standard error object that's used
*
* According to the docs, we are expected to check lastError in runtime API callbacks:
* "
* If you call an asynchronous function that may set lastError, you are expected to
* check for the error when you handle the result of the function. If lastError has been
* set and you don't check it within the callback function, then an error will be raised.
* "
*
* @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/lastError}
* @returns {Error|undefined}
*/
export function checkForLastError() {
const { lastError } = browser.runtime;
if (!lastError) {
return undefined;
}
// if it quacks like an Error, its an Error
if (lastError.stack && lastError.message) {
return lastError;
}
// repair incomplete error object (eg chromium v77)
return new Error(lastError.message);
}
/** @returns {Error|undefined} */
export function checkForLastErrorAndLog() {
const error = checkForLastError();
if (error) {
log.error(error);
}
return error;
}
/** @returns {Error|undefined} */
export function checkForLastErrorAndWarn() {
const error = checkForLastError();
if (error) {
console.warn(error);
}
return error;
}

@ -0,0 +1,54 @@
import sinon from 'sinon';
import browser from 'webextension-polyfill';
import log from 'loglevel';
import * as BrowserRuntimeUtil from './browser-runtime.utils';
const mockLastError = { message: 'error', stack: [] };
describe('Browser Runtime Utils', () => {
beforeAll(() => {
sinon.replace(browser, 'runtime', {
lastError: undefined,
});
});
describe('checkForLastError', () => {
it('should return undefined if no lastError found', () => {
expect(BrowserRuntimeUtil.checkForLastError()).toBeUndefined();
});
it('should return the lastError (Error object) if lastError is found', () => {
sinon.stub(browser.runtime, 'lastError').value(mockLastError);
expect(BrowserRuntimeUtil.checkForLastError()).toStrictEqual(
mockLastError,
);
});
it('should return an Error object if the lastError is found with no stack', () => {
sinon
.stub(browser.runtime, 'lastError')
.value({ message: mockLastError.message });
const result = BrowserRuntimeUtil.checkForLastError();
expect(result).toStrictEqual(expect.any(Error));
expect(result).toHaveProperty('stack');
expect(result.message).toBe(mockLastError.message);
});
});
describe('checkForLastErrorAndLog', () => {
it('should log and return error if error was found', () => {
sinon.stub(browser.runtime, 'lastError').value({ ...mockLastError });
sinon.stub(log, 'error');
const result = BrowserRuntimeUtil.checkForLastErrorAndLog();
expect(log.error.calledWith(result)).toBeTruthy();
expect(result).toStrictEqual(mockLastError);
log.error.restore();
});
});
});

@ -259,7 +259,7 @@ export async function determineTransactionAssetType(
assetType:
details.standard === TOKEN_STANDARDS.ERC20
? ASSET_TYPES.TOKEN
: ASSET_TYPES.COLLECTIBLE,
: ASSET_TYPES.NFT,
tokenStandard: details.standard,
};
}

@ -78,6 +78,10 @@ export const UI_NOTIFICATIONS = {
id: 15,
date: '2022-09-15',
},
16: {
id: 16,
date: null,
},
};
export const getTranslatedUINotifications = (t, locale) => {
@ -224,5 +228,16 @@ export const getTranslatedUINotifications = (t, locale) => {
)
: '',
},
16: {
...UI_NOTIFICATIONS[16],
title: t('notifications16Title'),
description: t('notifications16Description'),
actionText: t('notifications16ActionText'),
date: UI_NOTIFICATIONS[16].date
? new Intl.DateTimeFormat(formattedLocale).format(
new Date(UI_NOTIFICATIONS[16].date),
)
: '',
},
};
};

@ -12,12 +12,15 @@
"previousModalState": {
"name": null
}
}
},
"warning": null
},
"history": {
"mostRecentOverviewPage": "/"
"mostRecentOverviewPage": "/mostRecentOverviewPage"
},
"metamask": {
"usePhishDetect": true,
"participateInMetaMetrics": false,
"gasEstimateType": "fee-market",
"showBetaHeader": false,
"gasFeeEstimates": {
@ -47,6 +50,7 @@
"priorityFeeTrend": "down",
"networkCongestion": 0.90625
},
"snaps": [{}],
"preferences": {
"hideZeroBalanceTokens": false,
"showFiatInTestnets": false,
@ -242,20 +246,6 @@
"unapprovedEncryptionPublicKeyMsgCount": 0,
"unapprovedTypedMessages": {},
"unapprovedTypedMessagesCount": 0,
"send": {
"gasLimit": "0x5208",
"gasPrice": "0xee6b2800",
"gasTotal": "0x4c65c6294000",
"tokenBalance": null,
"from": "0xc42edfcc21ed14dda456aa0756c153f7985d8813",
"to": "",
"amount": "1bc16d674ec80000",
"memo": "",
"errors": {},
"maxModeOn": false,
"editingTransactionId": null,
"toNickname": ""
},
"useTokenDetection": true,
"advancedGasFee": {
"maxBaseFee": "75",
@ -1285,5 +1275,24 @@
"origin": "tmashuang.github.io"
}
]
},
"send": {
"amountMode": "INPUT",
"currentTransactionUUID": null,
"draftTransactions": {},
"eip1559support": false,
"gasEstimateIsLoading": true,
"gasEstimatePollToken": null,
"gasIsSetInModal": false,
"gasPriceEstimate": "0x0",
"gasLimitMinimum": "0x5208",
"gasTotalForLayer1": "0x0",
"recipientMode": "CONTACT_LIST",
"recipientInput": "",
"selectedAccount": {
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"balance": "0x0"
},
"stage": "INACTIVE"
}
}

@ -74,12 +74,12 @@ function defaultFixture() {
src: 'images/token-detection.svg',
width: '100%',
},
isShown: true,
isShown: false,
},
11: {
date: '2022-09-15',
id: 11,
isShown: true,
isShown: false,
},
12: {
date: '2022-05-18',
@ -98,11 +98,16 @@ function defaultFixture() {
14: {
date: '2022-09-15',
id: 14,
isShown: true,
isShown: false,
},
15: {
date: '2022-09-15',
id: 15,
isShown: false,
},
16: {
date: null,
id: 16,
isShown: true,
},
},
@ -225,7 +230,7 @@ function defaultFixture() {
selectedAddress: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1',
theme: 'light',
useBlockie: false,
useCollectibleDetection: false,
useNftDetection: false,
useNonceField: false,
usePhishDetect: true,
useTokenDetection: false,
@ -338,7 +343,7 @@ function onboardingFixture() {
},
theme: 'light',
useBlockie: false,
useCollectibleDetection: false,
useNftDetection: false,
useNonceField: false,
usePhishDetect: true,
useTokenDetection: false,
@ -407,9 +412,9 @@ class FixtureBuilder {
withCollectiblesController(data) {
merge(
this.fixture.data.CollectiblesController
? this.fixture.data.CollectiblesController
: (this.fixture.data.CollectiblesController = {}),
this.fixture.data.NftController
? this.fixture.data.NftController
: (this.fixture.data.NftController = {}),
data,
);
return this;

@ -51,6 +51,8 @@ async function withFixtures(options, testSuite) {
const phishingPageServer = new PhishingWarningPageServer();
let webDriver;
let driver;
const errors = [];
let failed = false;
try {
await ganacheServer.start(ganacheOptions);
@ -110,8 +112,12 @@ async function withFixtures(options, testSuite) {
) {
await ensureXServerIsRunning();
}
const { driver } = await buildWebDriver(driverOptions);
webDriver = driver;
driver = (await buildWebDriver(driverOptions)).driver;
webDriver = driver.driver;
if (process.env.SELENIUM_BROWSER === 'chrome') {
await driver.checkBrowserForExceptions();
}
await testSuite({
driver,
@ -120,7 +126,7 @@ async function withFixtures(options, testSuite) {
});
if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await driver.checkBrowserForConsoleErrors(driver);
errors.concat(await driver.checkBrowserForConsoleErrors(driver));
if (errors.length) {
const errorReports = errors.map((err) => err.message);
const errorMessage = `Errors found in browser console:\n${errorReports.join(
@ -137,10 +143,20 @@ async function withFixtures(options, testSuite) {
failed = true;
if (webDriver) {
try {
await webDriver.verboseReportOnFailure(title);
await driver.verboseReportOnFailure(title);
} catch (verboseReportError) {
console.error(verboseReportError);
}
if (
errors.length === 0 &&
driver.exceptions.length > 0 &&
failOnConsoleError
) {
const errorMessage = `Errors found in browser console:\n${driver.exceptions.join(
'\n',
)}`;
throw Error(errorMessage);
}
}
throw error;
} finally {
@ -151,7 +167,7 @@ async function withFixtures(options, testSuite) {
await secondaryGanacheServer.quit();
}
if (webDriver) {
await webDriver.quit();
await driver.quit();
}
if (dapp) {
for (let i = 0; i < numberOfDapps; i++) {

@ -458,10 +458,10 @@ describe('MetaMask', function () {
await driver.delay(veryLargeDelayMs);
await driver.clickElement({ text: 'Edit', tag: 'button' });
await driver.delay(veryLargeDelayMs);
await driver.clickElement(
{ text: 'Edit suggested gas fee', tag: 'button' },
10000,
);
await driver.clickElement({
text: 'Edit suggested gas fee',
tag: 'button',
});
await driver.delay(veryLargeDelayMs);
const inputs = await driver.findElements('input[type="number"]');
const gasLimitInput = inputs[0];
@ -576,10 +576,10 @@ describe('MetaMask', function () {
it('customizes gas', async function () {
await driver.clickElement('.confirm-approve-content__small-blue-text');
await driver.delay(regularDelayMs);
await driver.clickElement(
{ text: 'Edit suggested gas fee', tag: 'button' },
10000,
);
await driver.clickElement({
text: 'Edit suggested gas fee',
tag: 'button',
});
await driver.delay(regularDelayMs);
const [gasLimitInput, gasPriceInput] = await driver.findElements(

@ -42,7 +42,7 @@
},
"theme": "light",
"useBlockie": false,
"useCollectibleDetection": false,
"useNftDetection": false,
"useNonceField": false,
"usePhishDetect": true,
"useTokenDetection": false

@ -13,6 +13,14 @@ const getTestPathsForTestDir = async (testDir) => {
return testPaths;
};
function chunk(array, chunkSize) {
const result = [];
for (let i = 0; i < array.length; i += chunkSize) {
result.push(array.slice(i, i + chunkSize));
}
return result;
}
async function main() {
const { argv } = yargs(hideBin(process.argv))
.usage(
@ -66,7 +74,14 @@ async function main() {
args.push('--retries', retries);
}
for (const testPath of testPaths) {
// For running E2Es in parallel in CI
const currentChunkIndex = process.env.CIRCLE_NODE_INDEX ?? 0;
const totalChunks = process.env.CIRCLE_NODE_TOTAL ?? 1;
const chunkSize = Math.ceil(testPaths.length / totalChunks);
const chunks = chunk(testPaths, chunkSize);
const currentChunk = chunks[currentChunkIndex];
for (const testPath of currentChunk) {
await runInShell('node', [...args, testPath]);
}
}

@ -1,3 +1,3 @@
module.exports = {
TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/test-snaps/3.1.0',
TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/test-snaps/4.1.1/',
};

@ -15,10 +15,9 @@ describe('Test Snap bip-32', function () {
};
await withFixtures(
{
fixtures: new FixtureBuilder()
.withPermissionControllerConnectedToSnapDapp()
.build(),
fixtures: new FixtureBuilder().build(),
ganacheOptions,
failOnConsoleError: false,
title: this.test.title,
},
async ({ driver }) => {
@ -32,16 +31,36 @@ describe('Test Snap bip-32', function () {
await driver.driver.get(TEST_SNAPS_WEBSITE_URL);
await driver.delay(1000);
// find and scroll to the correct card and click first
const snapButton = await driver.findElement('#sendUpdateHello');
await driver.scrollToElement(snapButton);
await driver.delay(500);
await driver.fill('#snapId6', 'npm:@metamask/test-snap-bip32');
// find and scroll to the bip32 test and connect
const snapButton1 = await driver.findElement('#connectBip32');
await driver.scrollToElement(snapButton1);
await driver.delay(1000);
await driver.clickElement('#connectBip32');
// switch to metamask extension and click connect
let windowHandles = await driver.waitUntilXWindowHandles(
2,
1000,
10000,
);
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
);
await driver.clickElement(
{
text: 'Connect',
tag: 'button',
},
10000,
);
await driver.delay(2000);
// switch to metamask extension
windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000);
// approve install of snap
await driver.waitUntilXWindowHandles(2, 5000, 10000);
let windowHandles = await driver.getAllWindowHandles();
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
@ -61,18 +80,52 @@ describe('Test Snap bip-32', function () {
});
// switch back to test-snaps window
await driver.waitUntilXWindowHandles(1, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000);
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
// scroll to and click get public key
await driver.delay(1000);
const snapButton2 = await driver.findElement('#bip32GetPublic');
await driver.scrollToElement(snapButton2);
await driver.delay(1000);
await driver.clickElement('#bip32GetPublic');
// check for proper public key response
await driver.delay(1000);
const retrievePublicKeyResult1 = await driver.findElement(
'#bip32PublicKeyResult',
);
assert.equal(
await retrievePublicKeyResult1.getText(),
'"0x043e98d696ae15caef75fa8dd204a7c5c08d1272b2218ba3c20feeb4c691eec366606ece56791c361a2320e7fad8bcbb130f66d51c591fc39767ab2856e93f8dfb"',
);
// scroll to and click get compressed public key
await driver.delay(1000);
const snapButton3 = await driver.findElement(
'#bip32GetCompressedPublic',
);
await driver.scrollToElement(snapButton3);
await driver.delay(1000);
await driver.clickElement('#bip32GetCompressedPublic');
// check for proper public key response
await driver.delay(1000);
const retrievePublicKeyResult2 = await driver.findElement(
'#bip32PublicKeyResult',
);
assert.equal(
await retrievePublicKeyResult2.getText(),
'"0x033e98d696ae15caef75fa8dd204a7c5c08d1272b2218ba3c20feeb4c691eec366"',
);
// wait then run SECP256K1 test
await driver.delay(1000);
await driver.fill('#bip32SignMessage', 'foo bar');
await driver.clickElement('#sendBip32Secp256k1');
await driver.fill('#bip32Message-secp256k1', 'foo bar');
await driver.clickElement('#sendBip32-secp256k1');
// hit 'approve' on the custom confirm
await driver.waitUntilXWindowHandles(2, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000);
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
@ -82,27 +135,32 @@ describe('Test Snap bip-32', function () {
tag: 'button',
});
await driver.waitUntilXWindowHandles(1, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000);
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
// check result
await driver.delay(1000);
const secp256k1Result = await driver.findElement(
'#bip32Secp256k1Result',
'#bip32MessageResult-secp256k1',
);
assert.equal(
await secp256k1Result.getText(),
'Signature: "0xd30561eb9e3195e47d49198fb0bc66eda867a7dff4c5e8b60c2ec13851aa7d8cc3d485da177de63dad331f315d440cbb693a629efe228389c4693ea90465b101"',
'"0x3045022100b3ade2992ea3e5eb58c7550e9bddad356e9554233c8b099ebc3cb418e9301ae2022064746e15ae024808f0ba5d860e44dc4c97e65c8cba6f5ef9ea2e8c819930d2dc"',
);
// scroll further into messages section
await driver.delay(1000);
const snapButton4 = await driver.findElement('#bip32Message-ed25519');
await driver.scrollToElement(snapButton4);
await driver.delay(1000);
// wait then run ed25519 test
await driver.delay(1000);
await driver.clickElement('#sendBip32Ed25519');
await driver.fill('#bip32Message-ed25519', 'foo bar');
await driver.clickElement('#sendBip32-ed25519');
// hit 'approve' on the custom confirm
await driver.waitUntilXWindowHandles(2, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000);
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
@ -112,44 +170,17 @@ describe('Test Snap bip-32', function () {
tag: 'button',
});
await driver.waitUntilXWindowHandles(1, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000);
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
// check result
await driver.delay(1000);
const ed25519Result = await driver.findElement('#bip32Ed25519Result');
assert.equal(
await ed25519Result.getText(),
'Signature: "0xf3215b4d6c59aac7e01b4ceef530d1e2abf4857926b85a81aaae3894505699243768a887b7da4a8c2e0f25196196ba290b6531050db8dc15c252bdd508532a0a"',
);
const publicKeyButton = await driver.findElement('#sendBip32PublicKey');
await driver.scrollToElement(publicKeyButton);
// wait then run public key test
await driver.delay(1000);
await driver.clickElement('#sendBip32PublicKey');
// check result
await driver.delay(1000);
const publicKeyResult = await driver.findElement(
'#bip32PublicKeyResult',
);
assert.equal(
await publicKeyResult.getText(),
'Public key: "043e98d696ae15caef75fa8dd204a7c5c08d1272b2218ba3c20feeb4c691eec366606ece56791c361a2320e7fad8bcbb130f66d51c591fc39767ab2856e93f8dfb"',
);
// wait then run compressed public key test
await driver.delay(1000);
await driver.clickElement('#sendBip32CompressedPublicKey');
// check result
await driver.delay(1000);
const compressedPublicKeyResult = await driver.findElement(
'#bip32CompressedPublicKeyResult',
const ed25519Result = await driver.findElement(
'#bip32MessageResult-ed25519',
);
assert.equal(
await compressedPublicKeyResult.getText(),
'Public key: "033e98d696ae15caef75fa8dd204a7c5c08d1272b2218ba3c20feeb4c691eec366"',
await ed25519Result.getText(),
'"0xf3215b4d6c59aac7e01b4ceef530d1e2abf4857926b85a81aaae3894505699243768a887b7da4a8c2e0f25196196ba290b6531050db8dc15c252bdd508532a0a"',
);
},
);

@ -16,10 +16,9 @@ describe('Test Snap bip-44', function () {
};
await withFixtures(
{
fixtures: new FixtureBuilder()
.withPermissionControllerConnectedToSnapDapp()
.build(),
fixtures: new FixtureBuilder().build(),
ganacheOptions,
failOnConsoleError: false,
title: this.test.title,
},
async ({ driver }) => {
@ -31,19 +30,35 @@ describe('Test Snap bip-44', function () {
// navigate to test snaps page and connect
await driver.driver.get(TEST_SNAPS_WEBSITE_URL);
const snapButton1 = await driver.findElement('#connectBip44Snap');
await driver.scrollToElement(snapButton1);
await driver.delay(1000);
await driver.fill('#snapId3', 'npm:@metamask/test-snap-bip44');
await driver.clickElement('#connectBip44Snap');
const snapButton = await driver.findElement('#snapId3');
await driver.scrollToElement(snapButton);
await driver.delay(500);
// switch to metamask extension and click connect
let windowHandles = await driver.waitUntilXWindowHandles(
2,
1000,
10000,
);
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
);
await driver.clickElement(
{
text: 'Connect',
tag: 'button',
},
10000,
);
await driver.delay(2000);
// connect the snap
await driver.clickElement('#connectBip44');
// switch to metamask extension
windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000);
// approve install of snap
await driver.waitUntilXWindowHandles(2, 5000, 10000);
let windowHandles = await driver.getAllWindowHandles();
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
@ -62,18 +77,48 @@ describe('Test Snap bip-44', function () {
});
// click send inputs on test snap page
await driver.waitUntilXWindowHandles(1, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000);
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
await driver.delay(1000);
await driver.clickElement('#sendBip44');
await driver.clickElement('#sendBip44Test');
// check the results of the public key test
await driver.delay(2000);
await driver.delay(1000);
const bip44Result = await driver.findElement('#bip44Result');
assert.equal(
await bip44Result.getText(),
'Public key: "0x86debb44fb3a984d93f326131d4c1db0bc39644f1a67b673b3ab45941a1cea6a385981755185ac4594b6521e4d1e08d1"',
'"0x86debb44fb3a984d93f326131d4c1db0bc39644f1a67b673b3ab45941a1cea6a385981755185ac4594b6521e4d1e08d1"',
);
// enter a message to sign
await driver.fill('#bip44Message', '1234');
await driver.delay(1000);
const snapButton3 = await driver.findElement('#signBip44Message');
await driver.scrollToElement(snapButton3);
await driver.delay(1000);
await driver.clickElement('#signBip44Message');
// Switch to approve signature message window and approve
windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000);
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
);
await driver.clickElement({
text: 'Approve',
tag: 'button',
});
// switch back to test-snaps page
windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000);
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
await driver.delay(1000);
// check the results of the message signature
const bip44SignResult = await driver.findElement('#bip44SignResult');
assert.equal(
await bip44SignResult.getText(),
'"0xa41ab87ca50606eefd47525ad90294bbe44c883f6bc53655f1b8a55aa8e1e35df216f31be62e52c7a1faa519420e20810162e07dedb0fde2a4d997ff7180a78232ecd8ce2d6f4ba42ccacad33c5e9e54a8c4d41506bdffb2bb4c368581d8b086"',
);
},
);

@ -16,10 +16,9 @@ describe('Test Snap Confirm', function () {
};
await withFixtures(
{
fixtures: new FixtureBuilder()
.withPermissionControllerConnectedToSnapDapp()
.build(),
fixtures: new FixtureBuilder().build(),
ganacheOptions,
failOnConsoleError: false,
title: this.test.title,
},
async ({ driver }) => {
@ -31,12 +30,33 @@ describe('Test Snap Confirm', function () {
// navigate to test snaps page and connect
await driver.driver.get(TEST_SNAPS_WEBSITE_URL);
await driver.fill('#snapId1', 'npm:@metamask/test-snap-confirm');
await driver.clickElement('#connectHello');
const snapButton1 = await driver.findElement('#connectConfirmSnap');
await driver.scrollToElement(snapButton1);
await driver.delay(1000);
await driver.clickElement('#connectConfirmSnap');
// switch to metamask extension and click connect
let windowHandles = await driver.waitUntilXWindowHandles(
2,
1000,
10000,
);
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
);
await driver.clickElement(
{
text: 'Connect',
tag: 'button',
},
10000,
);
await driver.delay(2000);
// approve install of snap
await driver.waitUntilXWindowHandles(2, 5000, 10000);
let windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000);
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
@ -46,20 +66,18 @@ describe('Test Snap Confirm', function () {
tag: 'button',
});
// click send inputs on test snap page
await driver.waitUntilXWindowHandles(1, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
// switch back to test snaps page
windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000);
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
const snapButton = await driver.findElement('#sendConfirmButton');
await driver.scrollToElement(snapButton);
// click send inputs on test snap page
const snapButton2 = await driver.findElement('#sendConfirmButton');
await driver.scrollToElement(snapButton2);
await driver.delay(1000);
await driver.clickElement('#sendConfirmButton');
// hit 'approve' on the custom confirm
await driver.waitUntilXWindowHandles(2, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000);
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
@ -70,8 +88,7 @@ describe('Test Snap Confirm', function () {
});
// check the results of the custom confirm
await driver.waitUntilXWindowHandles(1, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000);
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
const confirmResult = await driver.findElement('#confirmResult');
assert.equal(await confirmResult.getText(), 'true');

@ -1,6 +1,5 @@
const { strict: assert } = require('assert');
const { withFixtures } = require('../helpers');
const { PAGES } = require('../webdriver/driver');
const FixtureBuilder = require('../fixture-builder');
const { TEST_SNAPS_WEBSITE_URL } = require('./enums');
@ -17,10 +16,9 @@ describe('Test Snap Error', function () {
};
await withFixtures(
{
fixtures: new FixtureBuilder()
.withPermissionControllerConnectedToSnapDapp()
.build(),
fixtures: new FixtureBuilder().build(),
ganacheOptions,
failOnConsoleError: false,
title: this.test.title,
},
async ({ driver }) => {
@ -31,17 +29,35 @@ describe('Test Snap Error', function () {
await driver.press('#password', driver.Key.ENTER);
// navigate to test snaps page and connect
await driver.driver.get(TEST_SNAPS_WEBSITE_URL);
await driver.fill('#snapId2', 'npm:@metamask/test-snap-error');
const snapButton = await driver.findElement('#connectError');
await driver.openNewPage(TEST_SNAPS_WEBSITE_URL);
const snapButton = await driver.findElement('#connectErrorSnap');
await driver.scrollToElement(snapButton);
await driver.delay(500);
await driver.delay(1000);
await driver.clickElement('#connectErrorSnap');
await driver.clickElement('#connectError');
// switch to metamask extension and click connect
let windowHandles = await driver.waitUntilXWindowHandles(
3,
1000,
10000,
);
const extensionPage = windowHandles[0];
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
);
await driver.clickElement(
{
text: 'Connect',
tag: 'button',
},
10000,
);
await driver.delay(2000);
// approve install of snap
let windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(3, 1000, 10000);
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
@ -52,14 +68,18 @@ describe('Test Snap Error', function () {
});
// click send inputs on test snap page
await driver.waitUntilXWindowHandles(1, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000);
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
await driver.delay(1000);
// find and click on send error
await driver.clickElement('#sendError');
await driver.navigate(PAGES.HOME);
// switch back to the extension page
await driver.switchToWindow(extensionPage);
await driver.delay(1000);
// look for the actual error and check if it is correct
const error = await driver.findElement(
'.home-notification__content-container',
);
@ -70,6 +90,12 @@ describe('Test Snap Error', function () {
),
true,
);
// try to click on the dismiss button and pass test if it works
await driver.clickElement({
text: 'Dismiss',
tag: 'button',
});
},
);
});

@ -3,8 +3,8 @@ const { withFixtures } = require('../helpers');
const FixtureBuilder = require('../fixture-builder');
const { TEST_SNAPS_WEBSITE_URL } = require('./enums');
describe('Test Snap Confirm', function () {
it('can pop up a snap confirm and get its result', async function () {
describe('Test Snap Installed', function () {
it('can tell if a snap is installed', async function () {
const ganacheOptions = {
accounts: [
{
@ -16,10 +16,9 @@ describe('Test Snap Confirm', function () {
};
await withFixtures(
{
fixtures: new FixtureBuilder()
.withPermissionControllerConnectedToSnapDapp()
.build(),
fixtures: new FixtureBuilder().build(),
ganacheOptions,
failOnConsoleError: false,
title: this.test.title,
},
async ({ driver }) => {
@ -30,13 +29,34 @@ describe('Test Snap Confirm', function () {
await driver.press('#password', driver.Key.ENTER);
// navigate to test snaps page and connect
await driver.driver.get(TEST_SNAPS_WEBSITE_URL);
await driver.fill('#snapId1', 'npm:@metamask/test-snap-confirm');
await driver.clickElement('#connectHello');
await driver.openNewPage(TEST_SNAPS_WEBSITE_URL);
await driver.delay(1000);
const confirmButton = await driver.findElement('#connectConfirmSnap');
await driver.scrollToElement(confirmButton);
await driver.clickElement('#connectConfirmSnap');
// switch to metamask extension and click connect
let windowHandles = await driver.waitUntilXWindowHandles(
3,
1000,
10000,
);
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
);
await driver.clickElement(
{
text: 'Connect',
tag: 'button',
},
10000,
);
await driver.delay(2000);
// approve install of snap
await driver.waitUntilXWindowHandles(2, 5000, 10000);
let windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(3, 1000, 10000);
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
@ -47,18 +67,31 @@ describe('Test Snap Confirm', function () {
});
// click send inputs on test snap page
await driver.waitUntilXWindowHandles(1, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000);
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
const errorButton = await driver.findElement('#connectError');
const errorButton = await driver.findElement('#connectErrorSnap');
await driver.scrollToElement(errorButton);
await driver.delay(1000);
await driver.fill('#snapId2', 'npm:@metamask/test-snap-error');
await driver.clickElement('#connectError');
await driver.clickElement('#connectErrorSnap');
// switch to metamask extension and click connect
windowHandles = await driver.waitUntilXWindowHandles(3, 1000, 10000);
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
);
await driver.clickElement(
{
text: 'Connect',
tag: 'button',
},
10000,
);
await driver.delay(2000);
// approve install of snap
await driver.waitUntilXWindowHandles(2, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
await driver.switchToWindowWithTitle(
'MetaMask Notification',
@ -69,19 +102,12 @@ describe('Test Snap Confirm', function () {
tag: 'button',
});
await driver.waitUntilXWindowHandles(1, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000);
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
const getInstalledSnapsButton = await driver.findElement(
'#getInstalledSnapsButton',
);
await driver.scrollToElement(getInstalledSnapsButton);
const result = await driver.findElement('#installedSnapsResult');
await driver.scrollToElement(result);
await driver.delay(1000);
await driver.clickElement('#getInstalledSnapsButton');
await driver.delay(1000);
const result = await driver.findElement('#getInstalledSnapsResult');
assert.equal(
await result.getText(),
'npm:@metamask/test-snap-confirm, npm:@metamask/test-snap-error',

@ -17,10 +17,9 @@ describe('Test Snap manageState', function () {
await withFixtures(
{
fixtures: new FixtureBuilder()
.withPermissionControllerConnectedToSnapDapp()
.build(),
fixtures: new FixtureBuilder().build(),
ganacheOptions,
failOnConsoleError: false,
title: this.test.title,
},
async ({ driver }) => {
@ -31,21 +30,36 @@ describe('Test Snap manageState', function () {
await driver.press('#password', driver.Key.ENTER);
// navigate to test snaps page, then fill in the snapId
await driver.driver.get(TEST_SNAPS_WEBSITE_URL);
await driver.openNewPage(TEST_SNAPS_WEBSITE_URL);
await driver.delay(1000);
await driver.fill('#snapId4', 'npm:@metamask/test-snap-managestate');
// find and scroll to the rest of the card
const snapButton = await driver.findElement('#snapId4');
await driver.scrollToElement(snapButton);
await driver.delay(500);
// connect the snap
// find and scroll to the connect button and click it
const snapButton1 = await driver.findElement('#connectManageState');
await driver.scrollToElement(snapButton1);
await driver.delay(1000);
await driver.clickElement('#connectManageState');
// switch to metamask extension and click connect
let windowHandles = await driver.waitUntilXWindowHandles(
3,
1000,
10000,
);
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
);
await driver.clickElement(
{
text: 'Connect',
tag: 'button',
},
10000,
);
await driver.delay(2000);
// approve install of snap
await driver.waitUntilXWindowHandles(2, 5000, 10000);
let windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(3, 1000, 10000);
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
@ -56,54 +70,47 @@ describe('Test Snap manageState', function () {
});
// fill and click send inputs on test snap page
await driver.waitUntilXWindowHandles(1, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000);
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
await driver.fill('#dataManageState', '23');
await driver.delay(1000);
await driver.clickElement('#sendManageState');
// check the results of the public key test
await driver.delay(500);
await driver.delay(1000);
const manageStateResult = await driver.findElement(
'#sendManageStateResult',
);
assert.equal(await manageStateResult.getText(), 'true');
// click get results
await driver.clickElement('#retrieveManageState');
// check the results
await driver.delay(500);
await driver.delay(1000);
const retrieveManageStateResult = await driver.findElement(
'#retrieveManageStateResult',
);
assert.equal(
await retrieveManageStateResult.getText(),
'{"testState":["23"]}',
'{ "testState": [ "23" ] }',
);
// click clear results
await driver.clickElement('#clearManageState');
// check if true
await driver.delay(500);
await driver.delay(1000);
const clearManageStateResult = await driver.findElement(
'#clearManageStateResult',
);
assert.equal(await clearManageStateResult.getText(), 'true');
// click get results again
await driver.clickElement('#retrieveManageState');
// check result array is empty
await driver.delay(500);
await driver.delay(1000);
const retrieveManageStateResult2 = await driver.findElement(
'#retrieveManageStateResult',
);
assert.equal(
await retrieveManageStateResult2.getText(),
'{"testState":[]}',
'{ "testState": [] }',
);
},
);

@ -1,6 +1,5 @@
const { strict: assert } = require('assert');
const { withFixtures } = require('../helpers');
const { PAGES } = require('../webdriver/driver');
const FixtureBuilder = require('../fixture-builder');
const { TEST_SNAPS_WEBSITE_URL } = require('./enums');
@ -17,10 +16,9 @@ describe('Test Snap Notification', function () {
};
await withFixtures(
{
fixtures: new FixtureBuilder()
.withPermissionControllerConnectedToSnapDapp()
.build(),
fixtures: new FixtureBuilder().build(),
ganacheOptions,
failOnConsoleError: false,
title: this.test.title,
},
async ({ driver }) => {
@ -31,21 +29,37 @@ describe('Test Snap Notification', function () {
await driver.press('#password', driver.Key.ENTER);
// navigate to test snaps page
await driver.driver.get(TEST_SNAPS_WEBSITE_URL);
await driver.openNewPage(TEST_SNAPS_WEBSITE_URL);
await driver.delay(1000);
// find and scroll down to snapId5
const snapButton = await driver.findElement('#snapId5');
// find and scroll down to snapId5 and connect
const snapButton = await driver.findElement('#connectNotification');
await driver.scrollToElement(snapButton);
await driver.delay(500);
await driver.fill('#snapId5', 'npm:@metamask/test-snap-notification');
// connect the snap
await driver.clickElement('#connectNotification');
// switch to metamask extension and click connect
let windowHandles = await driver.waitUntilXWindowHandles(
3,
1000,
10000,
);
const extensionPage = windowHandles[0];
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
);
await driver.clickElement(
{
text: 'Connect',
tag: 'button',
},
10000,
);
await driver.delay(2000);
// approve install of snap
await driver.waitUntilXWindowHandles(2, 5000, 10000);
let windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(3, 1000, 10000);
await driver.switchToWindowWithTitle(
'MetaMask Notification',
windowHandles,
@ -56,14 +70,13 @@ describe('Test Snap Notification', function () {
});
// click send inputs on test snap page
await driver.waitUntilXWindowHandles(1, 5000, 10000);
windowHandles = await driver.getAllWindowHandles();
windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000);
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
await driver.delay(1000);
await driver.clickElement('#sendInAppNotification');
// try to go to the MM pages
await driver.navigate(PAGES.HOME);
// switch back to the extension page
await driver.switchToWindow(extensionPage);
await driver.delay(1500);
// check to see that there is one notification
@ -74,14 +87,14 @@ describe('Test Snap Notification', function () {
// try to click on the account menu icon (via xpath)
await driver.clickElement('.account-menu__icon');
await driver.delay(500);
await driver.delay(1000);
// try to click on the notification item (via xpath)
await driver.clickElement({
text: 'Notifications',
tag: 'div',
});
await driver.delay(500);
await driver.delay(1000);
// look for the correct text in notifications (via xpath)
const notificationResultMessage = await driver.findElement(

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

Loading…
Cancel
Save