Merge remote-tracking branch 'origin/master' into Version-v10.12.0-alt

feature/default_network_editable
Dan Miller 3 years ago
commit d5c693d9db
  1. 8
      .circleci/config.yml
  2. 36
      .circleci/scripts/release-create-gh-release.sh
  3. 35
      CHANGELOG.md
  4. 3
      app/_locales/am/messages.json
  5. 3
      app/_locales/ar/messages.json
  6. 3
      app/_locales/bg/messages.json
  7. 3
      app/_locales/bn/messages.json
  8. 3
      app/_locales/ca/messages.json
  9. 3
      app/_locales/da/messages.json
  10. 6
      app/_locales/de/messages.json
  11. 9
      app/_locales/el/messages.json
  12. 37
      app/_locales/en/messages.json
  13. 9
      app/_locales/es/messages.json
  14. 9
      app/_locales/es_419/messages.json
  15. 3
      app/_locales/et/messages.json
  16. 3
      app/_locales/fa/messages.json
  17. 3
      app/_locales/fi/messages.json
  18. 3
      app/_locales/fil/messages.json
  19. 9
      app/_locales/fr/messages.json
  20. 3
      app/_locales/he/messages.json
  21. 9
      app/_locales/hi/messages.json
  22. 3
      app/_locales/hr/messages.json
  23. 3
      app/_locales/hu/messages.json
  24. 9
      app/_locales/id/messages.json
  25. 9
      app/_locales/it/messages.json
  26. 9
      app/_locales/ja/messages.json
  27. 3
      app/_locales/kn/messages.json
  28. 9
      app/_locales/ko/messages.json
  29. 3
      app/_locales/lt/messages.json
  30. 3
      app/_locales/lv/messages.json
  31. 3
      app/_locales/ms/messages.json
  32. 3
      app/_locales/no/messages.json
  33. 9
      app/_locales/ph/messages.json
  34. 3
      app/_locales/pl/messages.json
  35. 9
      app/_locales/pt_BR/messages.json
  36. 3
      app/_locales/ro/messages.json
  37. 9
      app/_locales/ru/messages.json
  38. 3
      app/_locales/sk/messages.json
  39. 3
      app/_locales/sl/messages.json
  40. 3
      app/_locales/sr/messages.json
  41. 3
      app/_locales/sv/messages.json
  42. 3
      app/_locales/sw/messages.json
  43. 9
      app/_locales/tl/messages.json
  44. 9
      app/_locales/tr/messages.json
  45. 3
      app/_locales/uk/messages.json
  46. 9
      app/_locales/vi/messages.json
  47. 9
      app/_locales/zh_CN/messages.json
  48. 3
      app/_locales/zh_TW/messages.json
  49. 2
      app/scripts/controllers/permissions/caveat-mutators.js
  50. 2
      app/scripts/controllers/permissions/caveat-mutators.test.js
  51. 5
      app/scripts/controllers/permissions/flask/snap-permissions.js
  52. 5
      app/scripts/controllers/permissions/specifications.js
  53. 4
      app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js
  54. 3
      app/scripts/metamask-controller.js
  55. 2
      development/metamaskbot-build-announce.js
  56. 77
      development/sentry-publish.js
  57. 17
      development/sentry-upload-artifacts.sh
  58. 8
      lavamoat/browserify/beta/policy.json
  59. 11
      lavamoat/browserify/flask/policy.json
  60. 8
      lavamoat/browserify/main/policy.json
  61. 18
      package.json
  62. 3
      shared/constants/permissions.js
  63. 12
      shared/constants/permissions.test.js
  64. 4
      test/e2e/helpers.js
  65. 5
      test/e2e/metamask-ui.spec.js
  66. 4
      test/e2e/tests/add-account.spec.js
  67. 4
      test/e2e/tests/metamask-responsive-ui.spec.js
  68. 22
      test/e2e/webdriver/driver.js
  69. 4
      ui/components/app/create-new-vault/create-new-vault.js
  70. 4
      ui/components/app/create-new-vault/create-new-vault.scss
  71. 2
      ui/components/app/flask/snap-settings-card/snap-settings-card.js
  72. 2
      ui/components/app/permission-page-container/index.scss
  73. 33
      ui/components/app/permissions-connect-header/permissions-connect-header.component.js
  74. 3
      ui/components/app/permissions-connect-permission-list/index.scss
  75. 231
      ui/components/app/srp-input/srp-input.js
  76. 67
      ui/components/app/srp-input/srp-input.scss
  77. 8
      ui/components/app/srp-input/srp-input.stories.js
  78. 1627
      ui/components/app/srp-input/srp-input.test.js
  79. 4
      ui/components/ui/icon-with-fallback/icon-with-fallback.component.js
  80. 43
      ui/components/ui/icon/icon-eye-slash.js
  81. 43
      ui/components/ui/icon/icon-eye.js
  82. 4
      ui/components/ui/icon/icon.stories.js
  83. 1
      ui/components/ui/show-hide-toggle/index.js
  84. 34
      ui/components/ui/show-hide-toggle/index.scss
  85. 86
      ui/components/ui/show-hide-toggle/show-hide-toggle.js
  86. 51
      ui/components/ui/show-hide-toggle/show-hide-toggle.stories.js
  87. 314
      ui/components/ui/show-hide-toggle/show-hide-toggle.test.js
  88. 8
      ui/components/ui/text-field/text-field.component.js
  89. 1
      ui/components/ui/ui-components.scss
  90. 4
      ui/pages/permissions-connect/flask/snap-install/index.scss
  91. 17
      ui/pages/permissions-connect/flask/snap-install/snap-install.js
  92. 2
      ui/pages/swaps/searchable-item-list/__snapshots__/searchable-item-list.test.js.snap
  93. 147
      yarn.lock

@ -160,6 +160,7 @@ workflows:
requires:
- prep-deps
- prep-build
- prep-build-flask
- all-tests-pass
- job-publish-storybook:
filters:
@ -718,8 +719,11 @@ jobs:
- attach_workspace:
at: .
- run:
name: sentry sourcemaps upload
command: SENTRY_ORG=metamask SENTRY_PROJECT=metamask yarn sentry:publish
name: Publish main release to Sentry
command: yarn sentry:publish
- run:
name: Publish Flask release to Sentry
command: yarn sentry:publish --build-type flask
- run:
name: Create GitHub release
command: |

@ -26,25 +26,53 @@ function install_github_cli ()
popd
}
function print_flask_version ()
{
local flask_filename
flask_filename="$(find ./builds-flask -type f -name 'metamask-flask-chrome-*.zip' -exec basename {} .zip \;)"
local flask_build_filename_prefix
flask_build_filename_prefix='metamask-flask-chrome-'
local flask_build_filename_prefix_size
flask_build_filename_prefix_size="${#flask_build_filename_prefix}"
# Use substring parameter expansion to remove the filename prefix, leaving just the version
echo "${flask_filename:$flask_build_filename_prefix_size}"
}
function publish_flask_tag ()
{
local flask_version="${1}"; shift
git config user.email "metamaskbot@users.noreply.github.com"
git config user.name "MetaMask Bot"
git tag -a "v${flask_version}" -m "Flask version ${flask_version}"
repo_slug="$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME"
git push "https://$GITHUB_TOKEN@github.com/$repo_slug" "v${flask_version}"
}
current_commit_msg=$(git show -s --format='%s' HEAD)
if [[ $current_commit_msg =~ Version[-[:space:]](v[[:digit:]]+.[[:digit:]]+.[[:digit:]]+) ]]
then
tag="${BASH_REMATCH[1]}"
flask_version="$(print_flask_version)"
install_github_cli
printf '%s\n' 'Creating GitHub Release'
release_body="$(awk -v version="${tag##v}" -f .circleci/scripts/show-changelog.awk CHANGELOG.md)"
pushd builds
hub release create \
--attach metamask-chrome-*.zip \
--attach metamask-firefox-*.zip \
--attach builds/metamask-chrome-*.zip \
--attach builds/metamask-firefox-*.zip \
--attach builds-flask/metamask-flask-chrome-*.zip \
--attach builds-flask/metamask-flask-firefox-*.zip \
--message "Version ${tag##v}" \
--message "$release_body" \
--commitish "$CIRCLE_SHA1" \
"$tag"
popd
publish_flask_tag "${flask_version}"
else
printf '%s\n' 'Version not found in commit message; skipping GitHub Release'
exit 0

@ -117,6 +117,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Harden keyring type check in EthOverview ([#13711](https://github.com/MetaMask/metamask-extension/pull/13711))
- Update "Forgot Password?" copy ([#13493](https://github.com/MetaMask/metamask-extension/pull/13493))
- Confirm transaction page: use method name only for contract transactions ([#13643](https://github.com/MetaMask/metamask-extension/pull/13643))
## [10.11.4]
### Added
- **[FLASK]** Snap removal confirmation ([#13619](https://github.com/MetaMask/metamask-extension/pull/13619))
### Changed
- **[FLASK]** Update MetaMask Flask
- This is the first release of [MetaMask Flask](https://metamask.io/flask) since the initial release on January 18. This release includes a significant number of fixes and DevX improvements. Flask will henceforth be released at a more frequent cadence, usually in close proximity to releases of the regular MetaMask Extension.
- For reference, [#13462](https://github.com/MetaMask/metamask-extension/pull/13462) used the feature branch that produced the original Flask release after some additional changes were made.
- **[FLASK]** Update Snaps packages to version `^0.10.6` ([#13901](https://github.com/MetaMask/metamask-extension/pull/13901), [#14041](https://github.com/MetaMask/metamask-extension/pull/14041), [#14070](https://github.com/MetaMask/metamask-extension/pull/14070))
- Updates the following packages from `0.9.0` to `0.10.6`:
- `@metamask/iframe-execution-environment-service`
- `@metamask/rpc-methods`
- `@metamask/snap-controllers`
- Updates the targeted [`iframe-execution-environment`](https://github.com/MetaMask/iframe-execution-environment) version from `0.3.1` to `0.4.2`.
- These changes encompass a variety of fixes and devX improvements. See the [releases](https://github.com/MetaMask/snaps-skunkworks/releases) of the Snaps monorepo for details.
### Fixed
- **[FLASK]** Various UI issues ([#13462](https://github.com/MetaMask/metamask-extension/pull/13462))
- _Note:_ The original Flask release was cut from the feature branch of [#13462](https://github.com/MetaMask/metamask-extension/pull/13462) before it was merged.
- Fix Snaps permission request confirmation page title ([#13342](https://github.com/MetaMask/metamask-extension/pull/13342))
- Fix Snaps custom confirmation `textarea` height ([#13572](https://github.com/MetaMask/metamask-extension/pull/13572))
- Fix various styling issues ([#13577](https://github.com/MetaMask/metamask-extension/pull/13577))
- **[FLASK]** Fix Snap key management install warning appearance ([#13844](https://github.com/MetaMask/metamask-extension/pull/13844))
## [10.11.3]
### Changed
- Split secret recovery phrase input into one-field-per-word ([#14016](https://github.com/MetaMask/metamask-extension/pull/14016))
## [10.11.2]
### Fixed
- Fix bug that users who are connected to another extension would hit when viewing connected sites ([#13974](https://github.com/MetaMask/metamask-extension/pull/13974))
## [10.11.1]
### Changed
@ -2889,6 +2920,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v10.12.0...HEAD
[10.12.0]: https://github.com/MetaMask/metamask-extension/compare/v10.11.1...v10.12.0
[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v10.11.4...HEAD
[10.11.4]: https://github.com/MetaMask/metamask-extension/compare/v10.11.3...v10.11.4
[10.11.3]: https://github.com/MetaMask/metamask-extension/compare/v10.11.2...v10.11.3
[10.11.2]: https://github.com/MetaMask/metamask-extension/compare/v10.11.1...v10.11.2
[10.11.1]: https://github.com/MetaMask/metamask-extension/compare/v10.11.0...v10.11.1
[10.11.0]: https://github.com/MetaMask/metamask-extension/compare/v10.10.2...v10.11.0
[10.10.2]: https://github.com/MetaMask/metamask-extension/compare/v10.10.1...v10.10.2

@ -801,9 +801,6 @@
"securityAndPrivacy": {
"message": "ደህንነት እና ግላዊነት"
},
"seedPhrasePlaceholder": {
"message": "እያንዳንዱን ቃል በነጠላ ክፍት ቦታ ይለያዩ"
},
"seedPhraseReq": {
"message": "የዘር ሐረጋት የ 12 ቃላት ርዝመት አላቸው"
},

@ -817,9 +817,6 @@
"securityAndPrivacy": {
"message": "الأمن والخصوصية"
},
"seedPhrasePlaceholder": {
"message": "يرجى فصل كل كلمة بمسافة واحدة"
},
"seedPhraseReq": {
"message": "طول الجمل البذرية 12 كلمة"
},

@ -812,9 +812,6 @@
"securityAndPrivacy": {
"message": "Сигурност и поверителност"
},
"seedPhrasePlaceholder": {
"message": "Отделете всяка дума с един интервал"
},
"seedPhraseReq": {
"message": "Фразите зародиш се състоят от 12 думи"
},

@ -816,9 +816,6 @@
"securityAndPrivacy": {
"message": "নিপত এবপনয়ত"
},
"seedPhrasePlaceholder": {
"message": "পরতিি শবদক একটিস দি আল করন"
},
"seedPhraseReq": {
"message": "সড ফজগি 12 শবর"
},

@ -794,9 +794,6 @@
"securityAndPrivacy": {
"message": "Seguretat i privacitat"
},
"seedPhrasePlaceholder": {
"message": "Separa cada paraula amb un únic espai"
},
"seedPhraseReq": {
"message": "Les frases de recuperació tenen 12 paraules"
},

@ -797,9 +797,6 @@
"securityAndPrivacy": {
"message": "Sikkerhed & Privatliv"
},
"seedPhrasePlaceholder": {
"message": "Adskil hvert ord med et enkelt mellemrum"
},
"seedPhraseReq": {
"message": "Backupsætninger er 12 ord lange"
},

@ -2343,9 +2343,6 @@
"seedPhraseIntroTitleCopy": {
"message": "Bevor Sie loslegen, schauen Sie sich dieses kurze Video an, um mehr über Ihre Geheime Wiederherstellungsphrase zu erfahren und wie Sie Ihre Wallet sicher halten können."
},
"seedPhrasePlaceholder": {
"message": "Trennen Sie jedes Wort durch ein einzelnes Leerzeichen."
},
"seedPhraseReq": {
"message": "Seed-Wörterfolgen bestehen aus 12 Wörtern"
},
@ -2453,9 +2450,6 @@
"showRecommendations": {
"message": "Empfehlungen anzeigen"
},
"showSeedPhrase": {
"message": "Geheime Wiederherstellungsphrase zeigen"
},
"showTestnetNetworks": {
"message": "Test-Netzwerke anzeigen"
},

@ -2343,12 +2343,6 @@
"seedPhraseIntroTitleCopy": {
"message": "Πριν ξεκινήσετε, παρακολουθήστε αυτό το σύντομο βίντεο για να μάθετε για τη Μυστική Φράση Ανάκτησης σας και πώς να κρατήσετε το πορτοφόλι σας ασφαλές."
},
"seedPhrasePlaceholder": {
"message": "Διαχωρίστε κάθε λέξη μ' ένα κενό"
},
"seedPhrasePlaceholderPaste": {
"message": "Επικόλληση Μυστικής Φράσης Ανάκτησης από το πρόχειρο"
},
"seedPhraseReq": {
"message": "Οι Μυστικές Φράσεις Ανάκτησης έχουν μήκος 12 λέξεων"
},
@ -2459,9 +2453,6 @@
"showRecommendations": {
"message": "Εμφάνιση Προτάσεων"
},
"showSeedPhrase": {
"message": "Εμφάνιση Μυστικής Φράσης Ανάκτησης"
},
"showTestnetNetworks": {
"message": "Εμφάνιση δοκιμαστικών δικτύων"
},

@ -2686,12 +2686,6 @@
"seedPhraseIntroTitleCopy": {
"message": "Before getting started, watch this short video to learn about your Secret Recovery Phrase and how to keep your wallet safe."
},
"seedPhrasePlaceholder": {
"message": "Separate each word with a single space"
},
"seedPhrasePlaceholderPaste": {
"message": "Enter your Secret Recovery Phrase"
},
"seedPhraseReq": {
"message": "Secret Recovery Phrases contain 12, 15, 18, 21, or 24 words"
},
@ -2769,6 +2763,10 @@
"settingsSearchMatchingNotFound": {
"message": "No matching results found"
},
"shorthandVersion": {
"message": "v$1",
"description": "$1 is replaced by a version string (e.g. 1.2.3)"
},
"show": {
"message": "Show"
},
@ -2808,9 +2806,6 @@
"showRecommendations": {
"message": "Show Recommendations"
},
"showSeedPhrase": {
"message": "Show Secret Recovery Phrase"
},
"showTestnetNetworks": {
"message": "Show test networks"
},
@ -2932,6 +2927,30 @@
"spendLimitTooLarge": {
"message": "Spend limit too large"
},
"srpInputNumberOfWords": {
"message": "I have a $1-word phrase",
"description": "This is the text for each option in the dropdown where a user selects how many words their secret recovery phrase has during import. The $1 is the number of words (either 12, 15, 18, 21, or 24)."
},
"srpPasteFailedTooManyWords": {
"message": "Paste failed because it contained over 24 words. A secret recovery phrase can have a maximum of 24 words.",
"description": "Description of SRP paste erorr when the pasted content has too many words"
},
"srpPasteTip": {
"message": "You can paste your entire secret recovery phrase into any field",
"description": "Our secret recovery phrase input is split into one field per word. This message explains to users that they can paste their entire secrete recovery phrase into any field, and we will handle it correctly."
},
"srpToggleShow": {
"message": "Show/Hide this word of the secret recovery phrase",
"description": "Describes a toggle that is used to show or hide a single word of the secret recovery phrase"
},
"srpWordHidden": {
"message": "This word is hidden",
"description": "Explains that a word in the secret recovery phrase is hidden"
},
"srpWordShown": {
"message": "This word is being shown",
"description": "Explains that a word in the secret recovery phrase is being shown"
},
"stable": {
"message": "Stable"
},

@ -1520,12 +1520,6 @@
"seedPhraseIntroTitleCopy": {
"message": "Antes de comenzar, mire este breve video para aprender sobre su frase de recuperación y sobre cómo mantener segura su cartera."
},
"seedPhrasePlaceholder": {
"message": "Separar cada palabra con un solo espacio"
},
"seedPhrasePlaceholderPaste": {
"message": "Pegar la frase secreta de recuperación desde el Portapapeles"
},
"seedPhraseReq": {
"message": "Las frases secretas de recuperación contienen 12, 15, 18, 21 o 24 palabras"
},
@ -1605,9 +1599,6 @@
"showPrivateKeys": {
"message": "Mostrar claves privadas"
},
"showSeedPhrase": {
"message": "Mostrar frase secreta de recuperación"
},
"sigRequest": {
"message": "Solicitud de firma"
},

@ -2392,12 +2392,6 @@
"seedPhraseIntroTitleCopy": {
"message": "Antes de comenzar, mire este breve video para aprender sobre su frase de recuperación y sobre cómo proteger su cartera."
},
"seedPhrasePlaceholder": {
"message": "Separar cada palabra con un solo espacio"
},
"seedPhrasePlaceholderPaste": {
"message": "Pegar la frase secreta de recuperación desde el Portapapeles"
},
"seedPhraseReq": {
"message": "Las frases secretas de recuperación contienen 12, 15, 18, 21 o 24 palabras"
},
@ -2511,9 +2505,6 @@
"showRecommendations": {
"message": "Mostrar recomendaciones"
},
"showSeedPhrase": {
"message": "Mostrar frase secreta de recuperación"
},
"showTestnetNetworks": {
"message": "Mostrar redes de prueba"
},

@ -806,9 +806,6 @@
"securityAndPrivacy": {
"message": "Turvalisus ja privaatsus"
},
"seedPhrasePlaceholder": {
"message": "Eraldage iga sõna ühe tühikuga"
},
"seedPhraseReq": {
"message": "Seemnefraasid on 12 sõna pikad"
},

@ -816,9 +816,6 @@
"securityAndPrivacy": {
"message": "امنیت و حریم خصوصی"
},
"seedPhrasePlaceholder": {
"message": "هر کلمه را با یک فاصله واحد جدا سازید"
},
"seedPhraseReq": {
"message": "عبارات بازیاب 12 کلمه اند"
},

@ -813,9 +813,6 @@
"securityAndPrivacy": {
"message": "Turva & yksityisyys"
},
"seedPhrasePlaceholder": {
"message": "Erota kukin sana yhdellä välilyönnillä"
},
"seedPhraseReq": {
"message": "Juurilauseet ovat 12 sanan pituisia"
},

@ -734,9 +734,6 @@
"securityAndPrivacy": {
"message": "Seguridad at Privacy"
},
"seedPhrasePlaceholder": {
"message": "Paghiwa-hiwalayin ang bawat salita gamit ang isang space"
},
"seedPhraseReq": {
"message": "Ang mga seed phrase ay may habang 12 salita"
},

@ -2343,12 +2343,6 @@
"seedPhraseIntroTitleCopy": {
"message": "Avant de commencer, regardez cette courte vidéo pour vous informer sur votre phrase secrète de récupération et sur la manière de sécuriser votre portefeuille."
},
"seedPhrasePlaceholder": {
"message": "Séparez chaque mot par un espace simple"
},
"seedPhrasePlaceholderPaste": {
"message": "Collez la Phrase secrète de récupération depuis le presse-papiers"
},
"seedPhraseReq": {
"message": "Les phrases secrètes de récupération sont composées de 12, 15, 18, 21 ou 24 mots"
},
@ -2459,9 +2453,6 @@
"showRecommendations": {
"message": "Afficher les recommandations"
},
"showSeedPhrase": {
"message": "Afficher la phrase secrète de récupération"
},
"showTestnetNetworks": {
"message": "Afficher les réseaux de test"
},

@ -810,9 +810,6 @@
"securityAndPrivacy": {
"message": "אבטחה ופרטיות"
},
"seedPhrasePlaceholder": {
"message": "הפרד/י בין המילים ברווח אחד"
},
"seedPhraseReq": {
"message": "צירופי גרעין מורכבים מ-12 מילים"
},

@ -2343,12 +2343,6 @@
"seedPhraseIntroTitleCopy": {
"message": "शआत करन पहल, अपनत रिकवरज और अपनट करकित रखन तरननिए यह छ-सि।"
},
"seedPhrasePlaceholder": {
"message": "परतक शबद क एक रिि अलग कर"
},
"seedPhrasePlaceholderPaste": {
"message": "किपबड सत रिकवरिपक"
},
"seedPhraseReq": {
"message": "गत रिकवरज म 12, 15, 18, 21 य 24 शबद ह"
},
@ -2459,9 +2453,6 @@
"showRecommendations": {
"message": "अनि"
},
"showSeedPhrase": {
"message": "गत रिकवरज दि"
},
"showTestnetNetworks": {
"message": "परषण नटवरक दि"
},

@ -809,9 +809,6 @@
"securityAndPrivacy": {
"message": "Sigurnost i privatnost"
},
"seedPhrasePlaceholder": {
"message": "Odvojite pojedinačne riječi jednim razmakom"
},
"seedPhraseReq": {
"message": "Početne rečenice imaju 12 riječi"
},

@ -809,9 +809,6 @@
"securityAndPrivacy": {
"message": "Biztonság és adatvédelem"
},
"seedPhrasePlaceholder": {
"message": "A szavakat egy-egy szóközzel válassza el"
},
"seedPhraseReq": {
"message": "A seed mondat 12 szóból áll"
},

@ -2343,12 +2343,6 @@
"seedPhraseIntroTitleCopy": {
"message": "Sebelum memulai, lihat video singkat ini untuk mempelajari tentang Frasa Pemulihan Rahasia Anda dan cara menjaga keamanan dompet Anda."
},
"seedPhrasePlaceholder": {
"message": "Pisahkan setiap kata dengan satu spasi"
},
"seedPhrasePlaceholderPaste": {
"message": "Tempel Frasa Pemulihan Rahasia dari clipboard"
},
"seedPhraseReq": {
"message": "Frasa Pemulihan Rahasia berisi 12, 15, 18, 21, atau 24 kata"
},
@ -2459,9 +2453,6 @@
"showRecommendations": {
"message": "Tampilkan Rekomendasi"
},
"showSeedPhrase": {
"message": "Tampilkan Frasa Pemulihan Rahasia"
},
"showTestnetNetworks": {
"message": "Tampilkan jaringan pengujian"
},

@ -1216,12 +1216,6 @@
"securityAndPrivacy": {
"message": "Sicurezza & Privacy"
},
"seedPhrasePlaceholder": {
"message": "Separa ogni parola con un singolo spazio"
},
"seedPhrasePlaceholderPaste": {
"message": "Incolla frase seed dagli appunti"
},
"seedPhraseReq": {
"message": "le frasi seed sono lunghe 12 parole"
},
@ -1298,9 +1292,6 @@
"showPrivateKeys": {
"message": "Mostra Chiave Privata"
},
"showSeedPhrase": {
"message": "Mostra frase seed"
},
"sigRequest": {
"message": "Firma Richiesta"
},

@ -2343,12 +2343,6 @@
"seedPhraseIntroTitleCopy": {
"message": "始める前に、この短いビデオを見て、シークレットリカバリーフレーズとウォレットを安全に保つ方法について確認してください。"
},
"seedPhrasePlaceholder": {
"message": "単語ごとにスペースを1つ置いて分離します"
},
"seedPhrasePlaceholderPaste": {
"message": "クリップボードからシークレット リカバリー フレーズを貼り付けます"
},
"seedPhraseReq": {
"message": "シークレットリカバリーフレーズは、12、15、18、21、24語で構成されます"
},
@ -2459,9 +2453,6 @@
"showRecommendations": {
"message": "推奨を表示"
},
"showSeedPhrase": {
"message": "シークレットリカバリーフレーズを表示"
},
"showTestnetNetworks": {
"message": "テストネットワークを表示"
},

@ -816,9 +816,6 @@
"securityAndPrivacy": {
"message": "ಭದರತ ಮತಯತ"
},
"seedPhrasePlaceholder": {
"message": "ಒಲಕ ಪರತಿ ಪದವನಪಡಿಿ"
},
"seedPhraseReq": {
"message": "ಸಗಳ 12 ಪದಗಳಷಘವಿ"
},

@ -2343,12 +2343,6 @@
"seedPhraseIntroTitleCopy": {
"message": "시작하기 전에 이 짧은 동영상을 보고 비밀 복구 구문과 지갑을 안전하게 보호하는 방법에 대해 알아보세요."
},
"seedPhrasePlaceholder": {
"message": "공백 한 칸으로 각 단어를 구분하세요."
},
"seedPhrasePlaceholderPaste": {
"message": "클립보드에서 비밀 복구 구문 붙여넣기"
},
"seedPhraseReq": {
"message": "비밀 복구 구문은 12, 15, 18, 21 또는 24개의 단어로 구성됩니다"
},
@ -2459,9 +2453,6 @@
"showRecommendations": {
"message": "추천 보기"
},
"showSeedPhrase": {
"message": "비밀 복구 구문 표시"
},
"showTestnetNetworks": {
"message": "테스트 네트워크 보기"
},

@ -816,9 +816,6 @@
"securityAndPrivacy": {
"message": "Sauga ir privatumas"
},
"seedPhrasePlaceholder": {
"message": "Kiekvieną žodį atskirkite viengubu tarpu"
},
"seedPhraseReq": {
"message": "Atkūrimo frazės yra 12 žodžių ilgio"
},

@ -812,9 +812,6 @@
"securityAndPrivacy": {
"message": "Drošība un konfidencialitāte"
},
"seedPhrasePlaceholder": {
"message": "Atdaliet katru vārdu ar vienu atstarpi"
},
"seedPhraseReq": {
"message": "Atkopšanas frāzes ir 12 vārdus garas"
},

@ -796,9 +796,6 @@
"securityAndPrivacy": {
"message": "Keselamatan & Privasi"
},
"seedPhrasePlaceholder": {
"message": "Pisahkan setiap perkataan dengan satu ruang"
},
"seedPhraseReq": {
"message": "Frasa benih panjangnya 12 patah perkataan"
},

@ -800,9 +800,6 @@
"securityAndPrivacy": {
"message": "Sikkerhet og personvern"
},
"seedPhrasePlaceholder": {
"message": "Skill hvert ord med ett enkelt mellomrom"
},
"seedPhraseReq": {
"message": "Mnemoniske gjenopprettingsfraser består av 12 ord "
},

@ -1545,12 +1545,6 @@
"seedPhraseIntroTitleCopy": {
"message": "Bago magsimula, panoorin ang maikling video na ito para matuto tungkol sa recovery phrase at kung paano panatilihing ligtas ang iyong wallet."
},
"seedPhrasePlaceholder": {
"message": "Paghiwa-hiwalayin ang bawat salita gamit ang isang space"
},
"seedPhrasePlaceholderPaste": {
"message": "I-paste ang Secret Recovery Phrase mula sa clipboard"
},
"seedPhraseReq": {
"message": "Ang mga Secret Recovery Phrase ay naglalaman ng 12, 15, 18, 21, o 24 na salita"
},
@ -1630,9 +1624,6 @@
"showPrivateKeys": {
"message": "Ipakita ang Mga Private Key"
},
"showSeedPhrase": {
"message": "Ipakita ang Secret Recovery Phrase"
},
"sigRequest": {
"message": "Request ng Signature"
},

@ -810,9 +810,6 @@
"securityAndPrivacy": {
"message": "Bezpieczeństwo i prywatność"
},
"seedPhrasePlaceholder": {
"message": "Oddziel słowa pojedynczą spacją"
},
"seedPhraseReq": {
"message": "Frazy seed mają 12 słów"
},

@ -2376,12 +2376,6 @@
"seedPhraseIntroTitleCopy": {
"message": "Antes de iniciar, assista a esse vídeo curto para aprender sobre sua Frase de Recuperação Secreta e sobre como manter sua carteira segura."
},
"seedPhrasePlaceholder": {
"message": "Separe cada palavra com um único espaço"
},
"seedPhrasePlaceholderPaste": {
"message": "Cole a Frase de recuperação secreta da área de transferência"
},
"seedPhraseReq": {
"message": "As Frases de Recuperação Secretas contêm 12, 15, 18, 21 ou 24 palavras"
},
@ -2495,9 +2489,6 @@
"showRecommendations": {
"message": "Mostrar recomendações"
},
"showSeedPhrase": {
"message": "Mostrar Frase de Recuperação Secreta"
},
"showTestnetNetworks": {
"message": "Mostrar redes de teste"
},

@ -803,9 +803,6 @@
"securityAndPrivacy": {
"message": "Securitate și confidențialitate"
},
"seedPhrasePlaceholder": {
"message": "Despărțiți fiecare cuvânt cu un spațiu"
},
"seedPhraseReq": {
"message": "Expresiile seed sunt lungi de 12 cuvinte"
},

@ -2343,12 +2343,6 @@
"seedPhraseIntroTitleCopy": {
"message": "Прежде чем приступить к делу, посмотрите это короткое видео о секретной фразе для восстановления и споособах обезопасить кошелек."
},
"seedPhrasePlaceholder": {
"message": "Отделяйте каждое слово одним пробелом"
},
"seedPhrasePlaceholderPaste": {
"message": "Вставить секретную фразу восстановления из буфера обмена"
},
"seedPhraseReq": {
"message": "Секретные фразы для восстановления содержат 12, 15, 18, 21 или 24 слова"
},
@ -2459,9 +2453,6 @@
"showRecommendations": {
"message": "Показать рекомендации"
},
"showSeedPhrase": {
"message": "Показать секретную фразу для восстановления"
},
"showTestnetNetworks": {
"message": "Показать тестовые сети"
},

@ -779,9 +779,6 @@
"securityAndPrivacy": {
"message": "Bezpečnosť a súkromie"
},
"seedPhrasePlaceholder": {
"message": "Každé slovo oddeľte jednou medzerou"
},
"seedPhraseReq": {
"message": "klíčové fráze mají 12 slov"
},

@ -804,9 +804,6 @@
"securityAndPrivacy": {
"message": "Varnost in zasebnost"
},
"seedPhrasePlaceholder": {
"message": "Vsako besedo ločite z enim presledkom"
},
"seedPhraseReq": {
"message": "Seed phrase mora biti dolg 12 besed"
},

@ -807,9 +807,6 @@
"securityAndPrivacy": {
"message": "Bezbednost i privatnost"
},
"seedPhrasePlaceholder": {
"message": "Odvojite svaku reč jednim razmakom"
},
"seedPhraseReq": {
"message": "Šifre za oporavak naloga (seed phrases) imaju 12 reči"
},

@ -800,9 +800,6 @@
"securityAndPrivacy": {
"message": "Säkerhet och integritet"
},
"seedPhrasePlaceholder": {
"message": "Separera varje ord med ett enda mellanslag."
},
"seedPhraseReq": {
"message": "Nyckelfraser är 12 ord långa."
},

@ -794,9 +794,6 @@
"securityAndPrivacy": {
"message": "Ulinzi na Faragha"
},
"seedPhrasePlaceholder": {
"message": "Tenganisha kila neno kwa nafasi moja"
},
"seedPhraseReq": {
"message": "Virai vianzio vina urefu wa maneno 12"
},

@ -2343,12 +2343,6 @@
"seedPhraseIntroTitleCopy": {
"message": "Bago magsimula, panoorin ang maikling video na ito upang malaman ang tungkol sa iyong Secret Recovery Phrase at paano mapapanatiling ligtas ang wallet mo."
},
"seedPhrasePlaceholder": {
"message": "Paghiwa-hiwalayin ang bawat salita gamit ang espasyo"
},
"seedPhrasePlaceholderPaste": {
"message": "I-paste ang Secret Recovery Phrase mula sa clipboard"
},
"seedPhraseReq": {
"message": "Ang mga Secret Recovery Phrase ay naglalaman ng 12, 15, 18, 21, o 24 na salita"
},
@ -2459,9 +2453,6 @@
"showRecommendations": {
"message": "Ipakita ang mga Rekomendasyon"
},
"showSeedPhrase": {
"message": "Ipakita ang Secret Recovery Phrase"
},
"showTestnetNetworks": {
"message": "Ipakita ang mga test network"
},

@ -2343,12 +2343,6 @@
"seedPhraseIntroTitleCopy": {
"message": "Başlamadan önce Gizli Kurtarma İfadeniz ve cüzdanınızı nasıl güvende tutacağınız hakkında bilgi edinmek için bu kısa videoyu izleyin."
},
"seedPhrasePlaceholder": {
"message": "Her kelimeyi tek bir boşluk ile ayırın"
},
"seedPhrasePlaceholderPaste": {
"message": "Hafıza panosundan Gizli Kurtarma İfadesini yapıştır"
},
"seedPhraseReq": {
"message": "Gizli Kurtarma İfadeleri 12, 15, 18, 21 veya 24 kelimeden oluşur"
},
@ -2459,9 +2453,6 @@
"showRecommendations": {
"message": "Önerileri Göster"
},
"showSeedPhrase": {
"message": "Gizli Kurtarma İfadesini Göster"
},
"showTestnetNetworks": {
"message": "Test ağlarını göster"
},

@ -816,9 +816,6 @@
"securityAndPrivacy": {
"message": "Безпека й конфіденційність"
},
"seedPhrasePlaceholder": {
"message": "Відділіть кожне слово одним пробілом"
},
"seedPhraseReq": {
"message": "Початкові фрази мають 12 слів"
},

@ -2343,12 +2343,6 @@
"seedPhraseIntroTitleCopy": {
"message": "Trước khi bắt đầu, hãy xem video ngắn này để tìm hiểu thêm về cụm mật khẩu khôi phục bí mật của bạn và cách bảo vệ ví của bạn."
},
"seedPhrasePlaceholder": {
"message": "Phân tách mỗi từ bằng một dấu cách"
},
"seedPhrasePlaceholderPaste": {
"message": "Dán Cụm mật khẩu khôi phục bí mật từ khay nhớ tạm"
},
"seedPhraseReq": {
"message": "Cụm mật khẩu khôi phục bí mật gồm 12, 15, 18, 21 hoặc 24 từ"
},
@ -2459,9 +2453,6 @@
"showRecommendations": {
"message": "Hiển Thị Đề Xuất"
},
"showSeedPhrase": {
"message": "Hiển thị Cụm mật khẩu khôi phục bí mật"
},
"showTestnetNetworks": {
"message": "Hiển thị các mạng thử nghiệm"
},

@ -2343,12 +2343,6 @@
"seedPhraseIntroTitleCopy": {
"message": "在开始之前,观看这个简短的视频来了解您的账户助记词以及如何保护您的钱包安全。"
},
"seedPhrasePlaceholder": {
"message": "用空格分隔每个单词"
},
"seedPhrasePlaceholderPaste": {
"message": "从剪贴板粘贴账户助记词"
},
"seedPhraseReq": {
"message": "账户助记词由 12、15、18、21 或 24 个单词组成"
},
@ -2459,9 +2453,6 @@
"showRecommendations": {
"message": "显示建议"
},
"showSeedPhrase": {
"message": "显示账户助记词"
},
"showTestnetNetworks": {
"message": "显示测试网络"
},

@ -798,9 +798,6 @@
"securityAndPrivacy": {
"message": "安全&隱私"
},
"seedPhrasePlaceholder": {
"message": "單詞之間請用空白分隔"
},
"seedPhraseReq": {
"message": "助憶詞為 12 個詞語"
},

@ -1,4 +1,4 @@
import { CaveatMutatorOperation } from '@metamask/snap-controllers';
import { CaveatMutatorOperation } from '@metamask/controllers';
import { CaveatTypes } from '../../../../shared/constants/permissions';
/**

@ -1,4 +1,4 @@
import { CaveatMutatorOperation } from '@metamask/snap-controllers';
import { CaveatMutatorOperation } from '@metamask/controllers';
import { CaveatTypes } from '../../../../shared/constants/permissions';
import { CaveatMutatorFactories } from './caveat-mutators';

@ -1,8 +1,9 @@
import { endowmentPermissionBuilders } from '@metamask/controllers';
import {
restrictedMethodPermissionBuilders,
selectHooks,
} from '@metamask/rpc-methods';
import { endowmentPermissionBuilders } from '@metamask/snap-controllers';
import { ExcludedSnapPermissions } from '../../../../../shared/constants/permissions';
/**
* @returns {Record<string, Record<string, unknown>>} All endowment permission
@ -24,9 +25,11 @@ export const buildSnapEndowmentSpecifications = () =>
export function buildSnapRestrictedMethodSpecifications(hooks) {
return Object.values(restrictedMethodPermissionBuilders).reduce(
(specifications, { targetKey, specificationBuilder, methodHooks }) => {
if (!ExcludedSnapPermissions.has(targetKey)) {
specifications[targetKey] = specificationBuilder({
methodHooks: selectHooks(hooks, methodHooks),
});
}
return specifications;
},
{},

@ -1,7 +1,4 @@
import {
constructPermission,
PermissionType,
} from '@metamask/snap-controllers';
import { constructPermission, PermissionType } from '@metamask/controllers';
import {
CaveatTypes,
RestrictedMethods,

@ -1,10 +1,10 @@
///: BEGIN:ONLY_INCLUDE_IN(flask)
import { handlers as permittedSnapMethods } from '@metamask/rpc-methods/dist/permitted';
///: END:ONLY_INCLUDE_IN
import { flatten } from 'lodash';
import { permissionRpcMethods } from '@metamask/snap-controllers';
import { permissionRpcMethods } from '@metamask/controllers';
import { selectHooks } from '@metamask/rpc-methods';
import { ethErrors } from 'eth-rpc-errors';
import { flatten } from 'lodash';
import { UNSUPPORTED_RPC_METHODS } from '../../../../shared/constants/network';
import localHandlers from './handlers';

@ -589,7 +589,7 @@ export default class MetamaskController extends EventEmitter {
this.workerController = new IframeExecutionService({
onError: this.onExecutionEnvironmentError.bind(this),
iframeUrl: new URL(
'https://metamask.github.io/iframe-execution-environment/0.3.1',
'https://metamask.github.io/iframe-execution-environment/0.4.2',
),
messenger: this.controllerMessenger.getRestricted({
name: 'ExecutionService',
@ -607,6 +607,7 @@ export default class MetamaskController extends EventEmitter {
`${this.permissionController.name}:getEndowments`,
`${this.permissionController.name}:getPermissions`,
`${this.permissionController.name}:hasPermission`,
`${this.permissionController.name}:hasPermissions`,
`${this.permissionController.name}:requestPermissions`,
`${this.permissionController.name}:revokeAllPermissions`,
],

@ -3,7 +3,7 @@ const { promises: fs } = require('fs');
const path = require('path');
const fetch = require('node-fetch');
const glob = require('fast-glob');
const VERSION = require('../dist/chrome/manifest.json').version; // eslint-disable-line import/no-unresolved
const VERSION = require('../package.json').version;
const { getHighlights } = require('./highlights');
start().catch(console.error);

@ -1,6 +1,11 @@
#!/usr/bin/env node
const VERSION = require('../dist/chrome/manifest.json').version; // eslint-disable-line import/no-unresolved
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
const { runCommand, runInShell } = require('./lib/run-command');
const { getVersion } = require('./lib/get-version');
const { BuildType } = require('./lib/build-type');
start().catch((error) => {
console.error(error);
@ -8,34 +13,63 @@ start().catch((error) => {
});
async function start() {
if (!process.env.SENTRY_ORG) {
throw new Error('Missing required "SENTRY_ORG" environment variable');
} else if (!process.env.SENTRY_PROJECT) {
throw new Error('Missing required "SENTRY_PROJECT" environment variable');
}
const { argv } = yargs(hideBin(process.argv)).usage(
'$0 [options]',
'Publish a release to Sentry',
(_yargs) =>
_yargs
.option('org', {
default: 'metamask',
description: 'The Sentry organization',
type: 'string',
})
.option('project', {
default: 'metamask',
description: 'The Sentry project to publish',
type: 'string',
})
.option('build-type', {
default: BuildType.main,
description: 'The MetaMask extension build type',
choices: Object.values(BuildType),
})
.option('build-version', {
default: 0,
description: 'The MetaMask extension build version',
type: 'number',
}),
);
const { buildType, buildVersion, org, project } = argv;
process.env.SENTRY_ORG = org;
process.env.SENTRY_PROJECT = project;
const authWorked = await checkIfAuthWorks();
if (!authWorked) {
throw new Error(`Sentry auth failed`);
}
const version = getVersion(buildType, buildVersion);
// check if version exists or not
const versionAlreadyExists = await checkIfVersionExists();
const versionAlreadyExists = await checkIfVersionExists(version);
// abort if versions exists
if (versionAlreadyExists) {
console.log(
`Version "${VERSION}" already exists on Sentry, skipping version creation`,
`Version "${version}" already exists on Sentry, skipping version creation`,
);
} else {
// create sentry release
console.log(`creating Sentry release for "${VERSION}"...`);
await runCommand('sentry-cli', ['releases', 'new', VERSION]);
console.log(`creating Sentry release for "${version}"...`);
await runCommand('sentry-cli', ['releases', 'new', version]);
console.log(
`removing any existing files from Sentry release "${VERSION}"...`,
`removing any existing files from Sentry release "${version}"...`,
);
await runCommand('sentry-cli', [
'releases',
'files',
VERSION,
version,
'delete',
'--all',
]);
@ -43,18 +77,23 @@ async function start() {
// check if version has artifacts or not
const versionHasArtifacts =
versionAlreadyExists && (await checkIfVersionHasArtifacts());
versionAlreadyExists && (await checkIfVersionHasArtifacts(version));
if (versionHasArtifacts) {
console.log(
`Version "${VERSION}" already has artifacts on Sentry, skipping sourcemap upload`,
`Version "${version}" already has artifacts on Sentry, skipping sourcemap upload`,
);
return;
}
const additionalUploadArgs = [];
if (buildType !== BuildType.main) {
additionalUploadArgs.push('--dist-directory', `dist-${buildType}`);
}
// upload sentry source and sourcemaps
await runInShell('./development/sentry-upload-artifacts.sh', [
'--release',
VERSION,
version,
...additionalUploadArgs,
]);
}
@ -64,17 +103,17 @@ async function checkIfAuthWorks() {
);
}
async function checkIfVersionExists() {
async function checkIfVersionExists(version) {
return await doesNotFail(() =>
runCommand('sentry-cli', ['releases', 'info', VERSION]),
runCommand('sentry-cli', ['releases', 'info', version]),
);
}
async function checkIfVersionHasArtifacts() {
async function checkIfVersionHasArtifacts(version) {
const [artifact] = await runCommand('sentry-cli', [
'releases',
'files',
VERSION,
version,
'list',
]);
// When there's no artifacts, we get a response from the shell like this ['', '']

@ -23,17 +23,20 @@ Upload JavaScript bundles and sourcemaps to Sentry
Options:
-h, --help Show help text
-r, --release <release> Sentry release to upload files to (defaults to 'VERSION' environment variable)
--dist-directory <path> The 'dist' directory to use. Defaults to 'dist'.
EOF
}
function upload_sourcemaps {
local release="${1}"; shift
local dist_directory="${1}"; shift
sentry-cli releases files "${release}" upload-sourcemaps ./dist/chrome/*.js ./dist/sourcemaps/ --rewrite --url-prefix 'metamask'
sentry-cli releases files "${release}" upload-sourcemaps "${dist_directory}"/chrome/*.js "${dist_directory}"/sourcemaps/ --rewrite --url-prefix 'metamask'
}
function main {
local release=VERSION
local dist_directory='dist'
while :; do
case "${1-default}" in
@ -51,6 +54,16 @@ function main {
release="${2}"
shift
;;
--dist-directory)
if [[ -z $2 ]]
then
printf "'dist-directory' option requires an argument.\\n" >&2
printf '%s\n' "${__SEE_HELP_MESSAGE__}" >&2
exit 1
fi
dist_directory="${2}"
shift
;;
*)
break
esac
@ -70,7 +83,7 @@ function main {
fi
printf 'uploading source files and sourcemaps for Sentry release "%s"...\n' "${release}"
upload_sourcemaps "${release}"
upload_sourcemaps "${release}" "${dist_directory}"
printf 'all done!\n'
}

@ -695,6 +695,7 @@
},
"@metamask/rpc-methods": {
"packages": {
"@metamask/controllers": true,
"@metamask/key-tree": true,
"@metamask/snap-controllers": true,
"eth-rpc-errors": true
@ -734,25 +735,24 @@
"clearTimeout": true,
"console.error": true,
"console.log": true,
"fetch": true,
"console.warn": true,
"setTimeout": true
},
"packages": {
"@metamask/controllers": true,
"@metamask/execution-environments": true,
"@metamask/object-multiplex": true,
"@metamask/obs-store": true,
"@metamask/post-message-stream": true,
"@metamask/safe-event-emitter": true,
"@metamask/snap-workers": true,
"ajv": true,
"buffer": true,
"concat-stream": true,
"cross-fetch": true,
"crypto-browserify": true,
"deep-freeze-strict": true,
"eth-rpc-errors": true,
"fast-deep-equal": true,
"gunzip-maybe": true,
"immer": true,
"json-rpc-engine": true,
"json-rpc-middleware-stream": true,
"nanoid": true,

@ -630,16 +630,15 @@
"@metamask/iframe-execution-environment-service": {
"globals": {
"clearTimeout": true,
"console.log": true,
"document.body.appendChild": true,
"document.createElement": true,
"document.getElementById": true,
"setTimeout": true
},
"packages": {
"@metamask/execution-environments": true,
"@metamask/post-message-stream": true,
"@metamask/snap-controllers": true,
"@metamask/snap-workers": true,
"json-rpc-engine": true,
"json-rpc-middleware-stream": true,
"nanoid": true,
@ -714,6 +713,7 @@
},
"@metamask/rpc-methods": {
"packages": {
"@metamask/controllers": true,
"@metamask/key-tree": true,
"@metamask/snap-controllers": true,
"eth-rpc-errors": true
@ -753,25 +753,24 @@
"clearTimeout": true,
"console.error": true,
"console.log": true,
"fetch": true,
"console.warn": true,
"setTimeout": true
},
"packages": {
"@metamask/controllers": true,
"@metamask/execution-environments": true,
"@metamask/object-multiplex": true,
"@metamask/obs-store": true,
"@metamask/post-message-stream": true,
"@metamask/safe-event-emitter": true,
"@metamask/snap-workers": true,
"ajv": true,
"buffer": true,
"concat-stream": true,
"cross-fetch": true,
"crypto-browserify": true,
"deep-freeze-strict": true,
"eth-rpc-errors": true,
"fast-deep-equal": true,
"gunzip-maybe": true,
"immer": true,
"json-rpc-engine": true,
"json-rpc-middleware-stream": true,
"nanoid": true,

@ -695,6 +695,7 @@
},
"@metamask/rpc-methods": {
"packages": {
"@metamask/controllers": true,
"@metamask/key-tree": true,
"@metamask/snap-controllers": true,
"eth-rpc-errors": true
@ -734,25 +735,24 @@
"clearTimeout": true,
"console.error": true,
"console.log": true,
"fetch": true,
"console.warn": true,
"setTimeout": true
},
"packages": {
"@metamask/controllers": true,
"@metamask/execution-environments": true,
"@metamask/object-multiplex": true,
"@metamask/obs-store": true,
"@metamask/post-message-stream": true,
"@metamask/safe-event-emitter": true,
"@metamask/snap-workers": true,
"ajv": true,
"buffer": true,
"concat-stream": true,
"cross-fetch": true,
"crypto-browserify": true,
"deep-freeze-strict": true,
"eth-rpc-errors": true,
"fast-deep-equal": true,
"gunzip-maybe": true,
"immer": true,
"json-rpc-engine": true,
"json-rpc-middleware-stream": true,
"nanoid": true,

@ -86,8 +86,8 @@
"3box/ipfs/ipld-zcash/zcash-bitcore-lib/elliptic": "^6.5.4",
"3box/ipfs/libp2p-mdns/multicast-dns/dns-packet": "^5.2.2",
"3box/ipfs/prometheus-gc-stats/gc-stats/node-pre-gyp/tar": "^6.1.2",
"3box/**/libp2p-crypto/node-forge": "^1.0.0",
"3box/**/libp2p-keychain/node-forge": "^1.0.0",
"3box/**/libp2p-crypto/node-forge": "^1.3.0",
"3box/**/libp2p-keychain/node-forge": "^1.3.0",
"3box/ipfs/libp2p-webrtc-star/socket.io/engine.io": "^4.0.0",
"analytics-node/axios": "^0.21.2",
"ganache-core/lodash": "^4.17.21",
@ -112,21 +112,21 @@
"@keystonehq/metamask-airgapped-keyring": "0.2.1",
"@material-ui/core": "^4.11.0",
"@metamask/contract-metadata": "^1.31.0",
"@metamask/controllers": "^26.0.0",
"@metamask/design-tokens": "^1.3.0",
"@metamask/controllers": "^27.0.0",
"@metamask/eth-ledger-bridge-keyring": "^0.10.0",
"@metamask/eth-token-tracker": "^4.0.0",
"@metamask/etherscan-link": "^2.1.0",
"@metamask/iframe-execution-environment-service": "^0.9.0",
"@metamask/iframe-execution-environment-service": "^0.10.6",
"@metamask/jazzicon": "^2.0.0",
"@metamask/logo": "^3.1.1",
"@metamask/obs-store": "^5.0.0",
"@metamask/post-message-stream": "^4.0.0",
"@metamask/providers": "^8.1.1",
"@metamask/rpc-methods": "^0.9.0",
"@metamask/rpc-methods": "^0.10.6",
"@metamask/slip44": "^2.0.0",
"@metamask/smart-transactions-controller": "^1.9.1",
"@metamask/snap-controllers": "^0.9.0",
"@metamask/snap-controllers": "^0.10.6",
"@ngraveio/bc-ur": "^1.1.6",
"@popperjs/core": "^2.4.0",
"@reduxjs/toolkit": "^1.6.2",
@ -423,7 +423,11 @@
"ganache>leveldown": false,
"geckodriver": true,
"react-devtools>electron": true,
"eth-trezor-keyring>trezor-connect>@trezor/transport>protobufjs": false
"eth-trezor-keyring>trezor-connect>@trezor/transport>protobufjs": false,
"@metamask/iframe-execution-environment-service>@metamask/execution-environments": false,
"@metamask/snap-controllers>@metamask/execution-environments": false,
"@metamask/iframe-execution-environment-service>@metamask/snap-controllers>@metamask/execution-environments": false,
"@metamask/rpc-methods>@metamask/snap-controllers>@metamask/execution-environments": false
}
}
}

@ -21,4 +21,7 @@ export const PermissionNamespaces = Object.freeze({
export const EndowmentPermissions = Object.freeze({
'endowment:network-access': 'endowment:network-access',
});
// Methods / permissions in external packages that we are temporarily excluding.
export const ExcludedSnapPermissions = new Set(['snap_notify']);
///: END:ONLY_INCLUDE_IN

@ -1,6 +1,10 @@
import { endowmentPermissionBuilders } from '@metamask/snap-controllers';
import { endowmentPermissionBuilders } from '@metamask/controllers';
import { restrictedMethodPermissionBuilders } from '@metamask/rpc-methods';
import { EndowmentPermissions, RestrictedMethods } from './permissions';
import {
EndowmentPermissions,
ExcludedSnapPermissions,
RestrictedMethods,
} from './permissions';
describe('EndowmentPermissions', () => {
it('has the expected permission keys', () => {
@ -15,7 +19,9 @@ describe('RestrictedMethods', () => {
expect(Object.keys(RestrictedMethods).sort()).toStrictEqual(
[
'eth_accounts',
...Object.keys(restrictedMethodPermissionBuilders),
...Object.keys(restrictedMethodPermissionBuilders).filter(
(targetKey) => !ExcludedSnapPermissions.has(targetKey),
),
].sort(),
);
});

@ -248,8 +248,8 @@ const completeImportSRPOnboardingFlow = async (
await driver.clickElement('.btn-secondary');
// Import Secret Recovery Phrase
await driver.fill(
'input[placeholder="Enter your Secret Recovery Phrase"]',
await driver.pasteIntoField(
'[data-testid="import-srp__srp-word-0"]',
seedPhrase,
);

@ -202,11 +202,10 @@ describe('MetaMask', function () {
await restoreSeedLink.click();
await driver.delay(regularDelayMs);
await driver.fill(
'input[placeholder="Enter your Secret Recovery Phrase"]',
await driver.pasteIntoField(
'[data-testid="import-srp__srp-word-0"]',
testSeedPhrase,
);
await driver.delay(regularDelayMs);
await driver.fill('#password', 'correct horse battery staple');
await driver.fill('#confirm-password', 'correct horse battery staple');

@ -129,8 +129,8 @@ describe('Add account', function () {
await restoreSeedLink.click();
await driver.delay(regularDelayMs);
await driver.fill(
'input[placeholder="Enter your Secret Recovery Phrase"]',
await driver.pasteIntoField(
'[data-testid="import-srp__srp-word-0"]',
testSeedPhrase,
);
await driver.delay(regularDelayMs);

@ -169,8 +169,8 @@ describe('Metamask Responsive UI', function () {
assert.equal(await restoreSeedLink.getText(), 'Forgot password?');
await restoreSeedLink.click();
await driver.fill(
'input[placeholder="Enter your Secret Recovery Phrase"]',
await driver.pasteIntoField(
'[data-testid="import-srp__srp-word-0"]',
testSeedPhrase,
);

@ -1,6 +1,6 @@
const { promises: fs } = require('fs');
const { strict: assert } = require('assert');
const { until, error: webdriverError, By } = require('selenium-webdriver');
const { until, error: webdriverError, By, Key } = require('selenium-webdriver');
const cssToXPath = require('css-to-xpath');
/**
@ -257,6 +257,26 @@ class Driver {
}
}
/**
* Paste a string into a field.
*
* @param {string} element - The element locator.
* @param {string} contentToPaste - The content to paste.
*/
async pasteIntoField(element, contentToPaste) {
// Throw if double-quote is present in content to paste
// so that we don't have to worry about escaping double-quotes
if (contentToPaste.includes('"')) {
throw new Error('Cannot paste content with double-quote');
}
// Click to focus the field
await this.clickElement(element);
await this.executeScript(
`navigator.clipboard.writeText("${contentToPaste}")`,
);
await this.fill(element, Key.chord(Key.CONTROL, 'v'));
}
// Navigation
async navigate(page = Driver.PAGES.HOME) {

@ -107,9 +107,8 @@ export default function CreateNewVault({
return (
<form className="create-new-vault__form" onSubmit={onImport}>
<div className="create-new-vault__srp-section">
<SrpInput onChange={setSeedPhrase} />
</div>
<div className="create-new-vault__create-password">
<TextField
id="password"
label={t('newPassword')}
@ -132,6 +131,7 @@ export default function CreateNewVault({
margin="normal"
largeLabel
/>
</div>
{includeTerms ? (
<div className="create-new-vault__terms">
<CheckBox

@ -2,12 +2,12 @@
&__form {
display: flex;
flex-direction: column;
width: 360px;
}
&__srp-section {
&__create-password {
display: flex;
flex-direction: column;
width: 360px;
}
&__terms {

@ -180,7 +180,7 @@ const SnapSettingsCard = ({
tag="span"
className="snap-settings-card__version"
>
v {version}
{t('shorthandVersion', [version])}
</Typography>
</>
)}

@ -53,6 +53,7 @@
}
&__requested {
width: 100%;
text-align: left;
}
@ -91,6 +92,7 @@
}
&__permissions-container {
width: 100%;
display: flex;
flex-direction: column;
margin-top: 38px;

@ -5,12 +5,24 @@ import Box from '../../ui/box';
import {
FLEX_DIRECTION,
JUSTIFY_CONTENT,
///: BEGIN:ONLY_INCLUDE_IN(flask)
COLORS,
TYPOGRAPHY,
TEXT_ALIGN,
///: END:ONLY_INCLUDE_IN
} from '../../../helpers/constants/design-system';
///: BEGIN:ONLY_INCLUDE_IN(flask)
import SnapsAuthorshipPill from '../flask/snaps-authorship-pill';
import Typography from '../../ui/typography';
///: END:ONLY_INCLUDE_IN
export default class PermissionsConnectHeader extends Component {
///: BEGIN:ONLY_INCLUDE_IN(flask)
static contextTypes = {
t: PropTypes.func,
};
///: END:ONLY_INCLUDE_IN
static propTypes = {
iconUrl: PropTypes.string,
iconName: PropTypes.string.isRequired,
@ -20,6 +32,7 @@ export default class PermissionsConnectHeader extends Component {
headerText: PropTypes.string,
///: BEGIN:ONLY_INCLUDE_IN(flask)
npmPackageName: PropTypes.string,
snapVersion: PropTypes.string,
///: END:ONLY_INCLUDE_IN
};
@ -47,10 +60,12 @@ export default class PermissionsConnectHeader extends Component {
headerText,
///: BEGIN:ONLY_INCLUDE_IN(flask)
npmPackageName,
snapVersion,
///: END:ONLY_INCLUDE_IN
} = this.props;
///: BEGIN:ONLY_INCLUDE_IN(flask)
const npmPackageUrl = `https://www.npmjs.com/package/${npmPackageName}`;
const { t } = this.context;
///: END:ONLY_INCLUDE_IN
return (
<Box
@ -71,6 +86,24 @@ export default class PermissionsConnectHeader extends Component {
) : null
///: END:ONLY_INCLUDE_IN
}
{
///: BEGIN:ONLY_INCLUDE_IN(flask)
snapVersion && (
<Typography
boxProps={{
margin: [2, 0],
}}
color={COLORS.TEXT_MUTED}
variant={TYPOGRAPHY.H7}
align={TEXT_ALIGN.CENTER}
tag="span"
className="version"
>
{t('shorthandVersion', [snapVersion])}
</Typography>
)
///: END:ONLY_INCLUDE_IN
}
<div className="permissions-connect-header__subtitle">{headerText}</div>
</Box>
);

@ -1,7 +1,10 @@
.permissions-connect-permission-list {
width: 100%;
.permission {
@include H6;
width: 100%;
padding-bottom: 16px;
border-bottom: 1px solid var(--Grey-100);
display: flex;

@ -4,96 +4,219 @@ import PropTypes from 'prop-types';
import { useI18nContext } from '../../../hooks/useI18nContext';
import TextField from '../../ui/text-field';
import { clearClipboard } from '../../../helpers/utils/util';
import CheckBox from '../../ui/check-box';
import ActionableMessage from '../../ui/actionable-message';
import Dropdown from '../../ui/dropdown';
import Typography from '../../ui/typography';
import { COLORS } from '../../../helpers/constants/design-system';
import ShowHideToggle from '../../ui/show-hide-toggle';
import { TYPOGRAPHY } from '../../../helpers/constants/design-system';
import { parseSecretRecoveryPhrase } from './parse-secret-recovery-phrase';
const { isValidMnemonic } = ethers.utils;
const defaultNumberOfWords = 12;
export default function SrpInput({ onChange }) {
const [srpError, setSrpError] = useState('');
const [draftSrp, setDraftSrp] = useState('');
const [showSrp, setShowSrp] = useState(false);
const [pasteFailed, setPasteFailed] = useState(false);
const [draftSrp, setDraftSrp] = useState(
new Array(defaultNumberOfWords).fill(''),
);
const [showSrp, setShowSrp] = useState(
new Array(defaultNumberOfWords).fill(false),
);
const [numberOfWords, setNumberOfWords] = useState(defaultNumberOfWords);
const t = useI18nContext();
const onSrpChange = useCallback(
(event) => {
const rawSrp = event.target.value;
(newDraftSrp) => {
let newSrpError = '';
const parsedSeedPhrase = parseSecretRecoveryPhrase(rawSrp);
const joinedDraftSrp = newDraftSrp.join(' ');
if (rawSrp) {
const wordCount = parsedSeedPhrase.split(/\s/u).length;
if (wordCount % 3 !== 0 || wordCount > 24 || wordCount < 12) {
if (newDraftSrp.some((word) => word !== '')) {
if (newDraftSrp.some((word) => word === '')) {
newSrpError = t('seedPhraseReq');
} else if (!isValidMnemonic(parsedSeedPhrase)) {
} else if (!isValidMnemonic(joinedDraftSrp)) {
newSrpError = t('invalidSeedPhrase');
}
}
setDraftSrp(rawSrp);
setDraftSrp(newDraftSrp);
setSrpError(newSrpError);
onChange(newSrpError ? '' : parsedSeedPhrase);
onChange(newSrpError ? '' : joinedDraftSrp);
},
[setDraftSrp, setSrpError, t, onChange],
);
const toggleShowSrp = useCallback(() => {
setShowSrp((currentShowSrp) => !currentShowSrp);
const toggleShowSrp = useCallback((index) => {
setShowSrp((currentShowSrp) => {
const newShowSrp = currentShowSrp.slice();
if (newShowSrp[index]) {
newShowSrp[index] = false;
} else {
newShowSrp.fill(false);
newShowSrp[index] = true;
}
return newShowSrp;
});
}, []);
const onSrpWordChange = useCallback(
(index, newWord) => {
if (pasteFailed) {
setPasteFailed(false);
}
const newSrp = draftSrp.slice();
newSrp[index] = newWord.trim();
onSrpChange(newSrp);
},
[draftSrp, onSrpChange, pasteFailed],
);
const onSrpPaste = useCallback(
(rawSrp) => {
const parsedSrp = parseSecretRecoveryPhrase(rawSrp);
let newDraftSrp = parsedSrp.split(' ');
if (newDraftSrp.length > 24) {
setPasteFailed(true);
return;
} else if (pasteFailed) {
setPasteFailed(false);
}
let newNumberOfWords = numberOfWords;
if (newDraftSrp.length !== numberOfWords) {
if (newDraftSrp.length < 12) {
newNumberOfWords = 12;
} else if (newDraftSrp.length % 3 === 0) {
newNumberOfWords = newDraftSrp.length;
} else {
newNumberOfWords =
newDraftSrp.length + (3 - (newDraftSrp.length % 3));
}
setNumberOfWords(newNumberOfWords);
}
if (newDraftSrp.length < newNumberOfWords) {
newDraftSrp = newDraftSrp.concat(
new Array(newNumberOfWords - newDraftSrp.length).fill(''),
);
}
setShowSrp(new Array(newNumberOfWords).fill(false));
onSrpChange(newDraftSrp);
clearClipboard();
},
[numberOfWords, onSrpChange, pasteFailed, setPasteFailed],
);
const numberOfWordsOptions = [];
for (let i = 12; i <= 24; i += 3) {
numberOfWordsOptions.push({
name: t('srpInputNumberOfWords', [`${i}`]),
value: `${i}`,
});
}
return (
<>
<label htmlFor="import-srp__srp" className="import-srp__srp-label">
<Typography>{t('secretRecoveryPhrase')}</Typography>
<div className="import-srp__container">
<label className="import-srp__srp-label">
<Typography variant={TYPOGRAPHY.H4}>
{t('secretRecoveryPhrase')}
</Typography>
</label>
{showSrp ? (
<textarea
id="import-srp__srp"
className="import-srp__srp-shown"
onChange={onSrpChange}
onPaste={clearClipboard}
value={draftSrp}
placeholder={t('seedPhrasePlaceholder')}
autoComplete="off"
<ActionableMessage
className="import-srp__paste-tip"
iconFillColor="#037dd6" // This is `--color-info-default`
message={t('srpPasteTip')}
useIcon
/>
<Dropdown
className="import-srp__number-of-words-dropdown"
onChange={(newSelectedOption) => {
const newNumberOfWords = parseInt(newSelectedOption, 10);
if (Number.isNaN(newNumberOfWords)) {
throw new Error('Unable to parse option as integer');
}
let newDraftSrp = draftSrp.slice(0, newNumberOfWords);
if (newDraftSrp.length < newNumberOfWords) {
newDraftSrp = newDraftSrp.concat(
new Array(newNumberOfWords - newDraftSrp.length).fill(''),
);
}
setNumberOfWords(newNumberOfWords);
setShowSrp(new Array(newNumberOfWords).fill(false));
onSrpChange(newDraftSrp);
}}
options={numberOfWordsOptions}
selectedOption={`${numberOfWords}`}
/>
) : (
<div className="import-srp__srp">
{[...Array(numberOfWords).keys()].map((index) => {
const id = `import-srp__srp-word-${index}`;
return (
<div key={index} className="import-srp__srp-word">
<label htmlFor={id} className="import-srp__srp-word-label">
<Typography>{`${index + 1}.`}</Typography>
</label>
<TextField
id="import-srp__srp"
type="password"
onChange={onSrpChange}
value={draftSrp}
placeholder={t('seedPhrasePlaceholderPaste')}
id={id}
data-testid={id}
type={showSrp[index] ? 'text' : 'password'}
onChange={(e) => {
e.preventDefault();
onSrpWordChange(index, e.target.value);
}}
value={draftSrp[index]}
autoComplete="off"
onPaste={clearClipboard}
onPaste={(event) => {
const newSrp = event.clipboardData.getData('text');
if (newSrp.trim().match(/\s/u)) {
event.preventDefault();
onSrpPaste(newSrp);
} else {
onSrpWordChange(index, newSrp);
}
}}
/>
<ShowHideToggle
id={`${id}-checkbox`}
ariaLabelHidden={t('srpWordHidden')}
ariaLabelShown={t('srpWordShown')}
shown={showSrp[index]}
data-testid={`${id}-checkbox`}
onChange={() => toggleShowSrp(index)}
title={t('srpToggleShow')}
/>
)}
</div>
);
})}
</div>
{srpError ? (
<Typography
color={COLORS.ERROR_DEFAULT}
tag="span"
<ActionableMessage
className="import-srp__srp-error"
>
{srpError}
</Typography>
iconFillColor="#d73a49" // This is `--color-error-default`
message={srpError}
type="danger"
useIcon
/>
) : null}
<div className="import-srp__show-srp">
<CheckBox
id="import-srp__show-srp-checkbox"
checked={showSrp}
onClick={toggleShowSrp}
title={t('showSeedPhrase')}
{pasteFailed ? (
<ActionableMessage
className="import-srp__srp-too-many-words-error"
iconFillColor="#d73a49" // This is `--color-error-default`
message={t('srpPasteFailedTooManyWords')}
primaryAction={{
label: t('dismiss'),
onClick: () => setPasteFailed(false),
}}
type="danger"
useIcon
/>
<label
className="import-srp__show-srp-label"
htmlFor="import-srp__show-srp-checkbox"
>
<Typography tag="span">{t('showSeedPhrase')}</Typography>
</label>
) : null}
</div>
</>
);
}

@ -1,24 +1,71 @@
.import-srp {
&__container {
display: grid;
grid-template-areas:
"title dropdown"
"paste-tip paste-tip"
"input input"
"error error"
"too-many-words-error too-many-words-error";
}
@media (max-width: 767px) {
&__container {
grid-template-areas:
"title"
"dropdown"
"paste-tip"
"input"
"error"
"too-many-words-error";
}
}
&__srp-label {
grid-area: title;
}
&__number-of-words-dropdown {
grid-area: dropdown;
}
&__paste-tip {
margin-bottom: 8px;
grid-area: paste-tip;
width: auto;
margin-left: auto;
margin-right: auto;
}
&__srp-shown {
@include Paragraph;
&__srp {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-area: input;
}
padding: 8px 16px;
@media (max-width: 767px) {
&__srp {
grid-template-columns: 1fr;
}
}
&__srp-error {
margin-top: 4px;
&__srp-word {
display: flex;
align-items: center;
margin: 8px;
}
&__show-srp {
margin-top: 16px;
margin-bottom: 16px;
&__srp-word-label {
width: 2em;
}
&__show-srp-label {
margin-left: 8px;
&__srp-error {
margin-top: 4px;
grid-area: error;
}
&__srp-too-many-words-error {
margin-top: 4px;
grid-area: too-many-words-error;
}
}

@ -4,18 +4,14 @@ import SrpInput from '.';
export default {
title: 'Components/App/SrpInput',
id: __filename,
component: SrpInput,
argTypes: {
onChange: { action: 'changed' },
},
component: SrpInput,
};
const Template = (args) => {
return (
<div style={{ width: '600px' }}>
<SrpInput {...args} />
</div>
);
return <SrpInput {...args} />;
};
export const DefaultStory = Template.bind({});

File diff suppressed because it is too large Load Diff

@ -23,14 +23,14 @@ const IconWithFallback = ({
src={icon}
style={style}
className={className}
alt={name.length ? name : 'icon'}
alt={name || 'icon'}
{...props}
/>
) : (
<span
className={classnames('icon-with-fallback__fallback', fallbackClassName)}
>
{name.length ? name.charAt(0).toUpperCase() : ''}
{name && name.length ? name.charAt(0).toUpperCase() : ''}
</span>
);
};

@ -0,0 +1,43 @@
import React from 'react';
import PropTypes from 'prop-types';
const IconEyeSlash = ({
size = 24,
color = 'currentColor',
ariaLabel,
className,
}) => (
// This SVG is copied from `@fortawesome/fontawesome-free@5.13.0/regular/eye-slash.svg`.
<svg
width={size}
height={size}
fill={color}
className={className}
aria-label={ariaLabel}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"
>
<path d="M634 471L36 3.51A16 16 0 0 0 13.51 6l-10 12.49A16 16 0 0 0 6 41l598 467.49a16 16 0 0 0 22.49-2.49l10-12.49A16 16 0 0 0 634 471zM296.79 146.47l134.79 105.38C429.36 191.91 380.48 144 320 144a112.26 112.26 0 0 0-23.21 2.47zm46.42 219.07L208.42 260.16C210.65 320.09 259.53 368 320 368a113 113 0 0 0 23.21-2.46zM320 112c98.65 0 189.09 55 237.93 144a285.53 285.53 0 0 1-44 60.2l37.74 29.5a333.7 333.7 0 0 0 52.9-75.11 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64c-36.7 0-71.71 7-104.63 18.81l46.41 36.29c18.94-4.3 38.34-7.1 58.22-7.1zm0 288c-98.65 0-189.08-55-237.93-144a285.47 285.47 0 0 1 44.05-60.19l-37.74-29.5a333.6 333.6 0 0 0-52.89 75.1 32.35 32.35 0 0 0 0 29.19C89.72 376.41 197.08 448 320 448c36.7 0 71.71-7.05 104.63-18.81l-46.41-36.28C359.28 397.2 339.89 400 320 400z" />
</svg>
);
IconEyeSlash.propTypes = {
/**
* The size of the Icon follows an 8px grid 2 = 16px, 3 = 24px etc
*/
size: PropTypes.number,
/**
* The color of the icon accepts design token css variables
*/
color: PropTypes.string,
/**
* An additional className to assign the Icon
*/
className: PropTypes.string,
/**
* The aria-label of the icon for accessibility purposes
*/
ariaLabel: PropTypes.string,
};
export default IconEyeSlash;

@ -0,0 +1,43 @@
import React from 'react';
import PropTypes from 'prop-types';
const IconEye = ({
size = 24,
color = 'currentColor',
ariaLabel,
className,
}) => (
// This SVG copied from `@fortawesome/fontawesome-free@5.13.0/regular/eye.svg`.
<svg
width={size}
height={size}
fill={color}
className={className}
aria-label={ariaLabel}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 576 512"
>
<path d="M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z" />
</svg>
);
IconEye.propTypes = {
/**
* The size of the Icon follows an 8px grid 2 = 16px, 3 = 24px etc
*/
size: PropTypes.number,
/**
* The color of the icon accepts design token css variables
*/
color: PropTypes.string,
/**
* An additional className to assign the Icon
*/
className: PropTypes.string,
/**
* The aria-label of the icon for accessibility purposes
*/
ariaLabel: PropTypes.string,
};
export default IconEye;

@ -26,6 +26,8 @@ import IconCaretLeft from './icon-caret-left';
import IconCaretRight from './icon-caret-right';
import IconCaretDown from './icon-caret-down';
import IconCaretUp from './icon-caret-up';
import IconEye from './icon-eye';
import IconEyeSlash from './icon-eye-slash';
export default {
title: 'Components/UI/Icon',
@ -117,6 +119,8 @@ export const DefaultStory = (args) => (
<IconItem Component={<IconCaretRight {...args} />} />
<IconItem Component={<IconCaretDown {...args} />} />
<IconItem Component={<IconCaretUp {...args} />} />
<IconItem Component={<IconEye {...args} />} />
<IconItem Component={<IconEyeSlash {...args} />} />
</div>
</Box>
</div>

@ -0,0 +1 @@
export { default } from './show-hide-toggle';

@ -0,0 +1,34 @@
.show-hide-toggle {
position: relative;
display: inline-flex;
&__input {
appearance: none;
+ .show-hide-toggle__label {
cursor: pointer;
user-select: none;
}
/* Focused when tabbing with keyboard */
&:focus,
&:focus-visible {
outline: none;
+ .show-hide-toggle__label {
outline: Highlight auto 1px;
}
}
&:disabled {
+ label {
opacity: 0.5;
cursor: auto;
}
}
}
&__icon {
color: var(--color-icon-default);
}
}

@ -0,0 +1,86 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import IconEye from '../icon/icon-eye';
import IconEyeSlash from '../icon/icon-eye-slash';
const ShowHideToggle = ({
id,
shown,
onChange,
ariaLabelHidden,
ariaLabelShown,
className,
'data-testid': dataTestId,
disabled,
title,
}) => {
return (
<div className={classnames('show-hide-toggle', className)}>
<input
className="show-hide-toggle__input"
id={id}
type="checkbox"
checked={shown}
onChange={onChange}
data-testid={dataTestId}
disabled={disabled}
/>
<label htmlFor={id} className="show-hide-toggle__label" title={title}>
{shown ? (
<IconEye
ariaLabel={ariaLabelShown}
className="show-hide-toggle__icon"
/>
) : (
<IconEyeSlash
ariaLabel={ariaLabelHidden}
className="show-hide-toggle__icon"
/>
)}
</label>
</div>
);
};
ShowHideToggle.propTypes = {
/**
* The id of the ShowHideToggle for htmlFor
*/
id: PropTypes.string.isRequired,
/**
* If the ShowHideToggle is in the "shown" state or not
*/
shown: PropTypes.bool.isRequired,
/**
* The onChange handler of the ShowHideToggle
*/
onChange: PropTypes.func.isRequired,
/**
* The aria-label of the icon representing the "hidden" state
*/
ariaLabelHidden: PropTypes.string.isRequired,
/**
* The aria-label of the icon representing the "shown" state
*/
ariaLabelShown: PropTypes.string.isRequired,
/**
* An additional className to give the ShowHideToggle
*/
className: PropTypes.string,
/**
* The data test id of the input
*/
'data-testid': PropTypes.string,
/**
* Whether the input is disabled or not
*/
disabled: PropTypes.bool,
/**
* The title for the toggle. This is shown in a tooltip on hover.
*/
title: PropTypes.string,
};
export default ShowHideToggle;

@ -0,0 +1,51 @@
import React from 'react';
import { useArgs } from '@storybook/client-api';
import ShowHideToggle from '.';
export default {
title: 'Components/UI/ShowHideToggle', // title should follow the folder structure location of the component. Don't use spaces.
id: __filename,
argTypes: {
id: {
control: 'text',
},
ariaLabelHidden: {
control: 'text',
},
ariaLabelShown: {
control: 'text',
},
className: {
control: 'text',
},
dataTestId: {
control: 'text',
},
disabled: {
control: 'boolean',
},
onChange: {
action: 'onChange',
},
shown: {
control: 'boolean',
},
},
};
export const DefaultStory = (args) => {
const [{ shown }, updateArgs] = useArgs();
const handleOnToggle = () => {
updateArgs({ shown: !shown });
};
return <ShowHideToggle {...args} shown={shown} onChange={handleOnToggle} />;
};
DefaultStory.args = {
id: 'showHideToggle',
ariaLabelHidden: 'hidden',
ariaLabelShown: 'shown',
shown: false,
};
DefaultStory.storyName = 'Default';

@ -0,0 +1,314 @@
import React from 'react';
import { isInaccessible, render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import ShowHideToggle from '.';
describe('ShowHideToggle', () => {
beforeEach(() => {
jest.resetAllMocks();
});
it('should set title', async () => {
const onChange = jest.fn();
const { queryByTitle } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown
onChange={onChange}
title="example-title"
/>,
);
expect(queryByTitle('example-title')).toBeInTheDocument();
});
it('should set test ID', async () => {
const onChange = jest.fn();
const { queryByTestId } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown
onChange={onChange}
data-testid="example-test-id"
/>,
);
expect(queryByTestId('example-test-id')).toBeInTheDocument();
});
it('should show correct aria-label when shown', () => {
const onChange = jest.fn();
const { queryByLabelText } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown
onChange={onChange}
/>,
);
expect(queryByLabelText('hidden')).not.toBeInTheDocument();
expect(queryByLabelText('shown')).toBeInTheDocument();
});
it('should show correct aria-label when hidden', () => {
const onChange = jest.fn();
const { queryByLabelText } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown={false}
onChange={onChange}
/>,
);
expect(queryByLabelText('hidden')).toBeInTheDocument();
expect(queryByLabelText('shown')).not.toBeInTheDocument();
});
it('should show correct checkbox state when shown', () => {
const onChange = jest.fn();
const { queryByRole } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown
onChange={onChange}
/>,
);
expect(queryByRole('checkbox')).toBeChecked();
});
it('should show correct checkbox state when hidden', () => {
const onChange = jest.fn();
const { queryByRole } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown={false}
onChange={onChange}
/>,
);
expect(queryByRole('checkbox')).not.toBeChecked();
});
describe('enabled', () => {
it('should show checkbox as enabled', () => {
const onChange = jest.fn();
const { queryByRole } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown
onChange={onChange}
/>,
);
expect(queryByRole('checkbox')).toBeEnabled();
});
it('should be accessible', () => {
const onChange = jest.fn();
const { queryByRole } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown
onChange={onChange}
/>,
);
expect(isInaccessible(queryByRole('checkbox'))).toBeFalsy();
});
describe('shown', () => {
it('should call onChange when clicked', async () => {
const onChange = jest.fn();
const { queryByRole } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown
onChange={onChange}
/>,
);
await userEvent.click(queryByRole('checkbox'));
expect(onChange).toHaveBeenCalledTimes(1);
});
it('should call onChange on space', async () => {
const onChange = jest.fn();
const { queryByRole } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown
onChange={onChange}
/>,
);
queryByRole('checkbox').focus();
await userEvent.keyboard('[Space]');
expect(onChange).toHaveBeenCalledTimes(1);
});
});
describe('hidden', () => {
it('should call onChange when clicked', async () => {
const onChange = jest.fn();
const { queryByRole } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown={false}
onChange={onChange}
/>,
);
await userEvent.click(queryByRole('checkbox'));
expect(onChange).toHaveBeenCalledTimes(1);
});
it('should call onChange on space', async () => {
const onChange = jest.fn();
const { queryByRole } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown={false}
onChange={onChange}
/>,
);
queryByRole('checkbox').focus();
await userEvent.keyboard('[Space]');
expect(onChange).toHaveBeenCalledTimes(1);
});
});
});
describe('disabled', () => {
it('should show checkbox as disabled', () => {
const onChange = jest.fn();
const { queryByRole } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown
disabled
onChange={onChange}
/>,
);
expect(queryByRole('checkbox')).toBeDisabled();
});
it('should be accessible', () => {
const onChange = jest.fn();
const { queryByRole } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown
disabled
onChange={onChange}
/>,
);
expect(isInaccessible(queryByRole('checkbox'))).toBeFalsy();
});
describe('shown', () => {
it('should not call onChange when clicked', async () => {
const onChange = jest.fn();
const { queryByRole } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown
disabled
onChange={onChange}
/>,
);
await userEvent.click(queryByRole('checkbox'));
expect(onChange).not.toHaveBeenCalled();
});
it('should not call onChange on space', async () => {
const onChange = jest.fn();
const { queryByRole } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown
disabled
onChange={onChange}
/>,
);
queryByRole('checkbox').focus();
await userEvent.keyboard('[Space]');
expect(onChange).not.toHaveBeenCalled();
});
});
describe('hidden', () => {
it('should not call onChange when clicked', async () => {
const onChange = jest.fn();
const { queryByRole } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown={false}
disabled
onChange={onChange}
/>,
);
await userEvent.click(queryByRole('checkbox'));
expect(onChange).not.toHaveBeenCalled();
});
it('should not call onChange on space', async () => {
const onChange = jest.fn();
const { queryByRole } = render(
<ShowHideToggle
id="example"
ariaLabelHidden="hidden"
ariaLabelShown="shown"
shown={false}
disabled
onChange={onChange}
/>,
);
queryByRole('checkbox').focus();
await userEvent.keyboard('[Space]');
expect(onChange).not.toHaveBeenCalled();
});
});
});
});

@ -204,6 +204,7 @@ const themeToInputProps = {
};
const TextField = ({
'data-testid': dataTestId,
error,
classes,
theme,
@ -228,7 +229,7 @@ const TextField = ({
autoComplete,
});
if (onPaste) {
if (onPaste || dataTestId) {
if (!inputProps.InputProps) {
inputProps.InputProps = {};
}
@ -236,6 +237,7 @@ const TextField = ({
inputProps.InputProps.inputProps = {};
}
inputProps.InputProps.inputProps.onPaste = onPaste;
inputProps.InputProps.inputProps['data-testid'] = dataTestId;
}
return (
@ -255,6 +257,10 @@ TextField.defaultProps = {
};
TextField.propTypes = {
/**
* A test ID that gets set on the input element
*/
'data-testid': PropTypes.string,
/**
* Show error message
*/

@ -44,6 +44,7 @@
@import 'radio-group/index';
@import 'readonly-input/index';
@import 'sender-to-recipient/index';
@import 'show-hide-toggle/index.scss';
@import 'snackbar/index';
@import 'site-origin/index';
@import 'slider/index';

@ -22,6 +22,10 @@
}
}
.version {
font-family: monospace;
}
.page-container__footer {
width: 100%;
margin-top: 12px;

@ -75,16 +75,18 @@ export default function SnapInstall({
headerText={null} // TODO(ritave): Add header text when snaps support description
siteOrigin={targetSubjectMetadata.origin}
npmPackageName={npmId}
snapVersion={targetSubjectMetadata.version}
boxProps={{ alignItems: ALIGN_ITEMS.CENTER }}
/>
<Typography></Typography>
<Box
className="snap-requests-permission"
padding={4}
tag={TYPOGRAPHY.H7}
<Typography
boxProps={{
padding: [4, 4, 0, 4],
}}
variant={TYPOGRAPHY.H7}
tag="span"
>
<span>{t('snapRequestsPermission')}</span>
</Box>
{t('snapRequestsPermission')}
</Typography>
<PermissionsConnectPermissionList
permissions={request.permissions || {}}
/>
@ -149,5 +151,6 @@ SnapInstall.propTypes = {
name: PropTypes.string,
origin: PropTypes.string.isRequired,
sourceCode: PropTypes.string,
version: PropTypes.string,
}).isRequired,
};

@ -3,7 +3,6 @@
exports[`SearchableItemList renders the component with initial props 1`] = `
<div
class="MuiFormControl-root MuiTextField-root searchable-item-list__search MuiFormControl-fullWidth"
data-testid="search-list-items"
>
<div
class="MuiInputBase-root MuiInput-root TextField-inputRoot-12 MuiInputBase-fullWidth MuiInput-fullWidth Mui-focused Mui-focused TextField-inputFocused-11 MuiInputBase-formControl MuiInput-formControl MuiInputBase-adornedStart"
@ -23,6 +22,7 @@ exports[`SearchableItemList renders the component with initial props 1`] = `
aria-invalid="false"
autocomplete="off"
class="MuiInputBase-input MuiInput-input MuiInputBase-inputAdornedStart"
data-testid="search-list-items"
dir="auto"
type="text"
value=""

@ -2747,6 +2747,46 @@
web3 "^0.20.7"
web3-provider-engine "^16.0.3"
"@metamask/controllers@^27.0.0":
version "27.0.0"
resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-27.0.0.tgz#23fb24960880047635a7e0b226375b843f385ad1"
integrity sha512-ZMSniSWVJN6TGFwZv8o/N3jff0QKxW6/ZkhyE0Ikd6WB9WKt665OD/FgWbsJVBpzJ5fF4N7pziYULbVsag8pVQ==
dependencies:
"@ethereumjs/common" "^2.3.1"
"@ethereumjs/tx" "^3.2.1"
"@metamask/contract-metadata" "^1.31.0"
"@metamask/metamask-eth-abis" "3.0.0"
"@metamask/types" "^1.1.0"
"@types/uuid" "^8.3.0"
abort-controller "^3.0.0"
async-mutex "^0.2.6"
babel-runtime "^6.26.0"
deep-freeze-strict "^1.1.1"
eth-ens-namehash "^2.0.8"
eth-json-rpc-infura "^5.1.0"
eth-keyring-controller "^6.2.1"
eth-method-registry "1.1.0"
eth-phishing-detect "^1.1.14"
eth-query "^2.1.2"
eth-rpc-errors "^4.0.0"
eth-sig-util "^3.0.0"
ethereumjs-util "^7.0.10"
ethereumjs-wallet "^1.0.1"
ethers "^5.4.1"
ethjs-unit "^0.1.6"
fast-deep-equal "^3.1.3"
immer "^9.0.6"
isomorphic-fetch "^3.0.0"
json-rpc-engine "^6.1.0"
jsonschema "^1.2.4"
multiformats "^9.5.2"
nanoid "^3.1.31"
punycode "^2.1.1"
single-call-balance-checker-abi "^1.0.0"
uuid "^8.3.2"
web3 "^0.20.7"
web3-provider-engine "^16.0.3"
"@metamask/design-tokens@^1.3.0":
version "1.4.2"
resolved "https://registry.yarnpkg.com/@metamask/design-tokens/-/design-tokens-1.4.2.tgz#023030f3eca181b10bf89c5813a9656f4e7e2852"
@ -2811,22 +2851,37 @@
resolved "https://registry.yarnpkg.com/@metamask/etherscan-link/-/etherscan-link-2.1.0.tgz#c0be8e68445b7b83cf85bcc03a56cdf8e256c973"
integrity sha512-ADuWlTUkFfN2vXlz81Bg/0BA+XRor+CdK1055p6k7H6BLIPoDKn9SBOFld9haQFuR9cKh/JYHcnlSIv5R4fUEw==
"@metamask/execution-environments@^0.10.6":
version "0.10.6"
resolved "https://registry.yarnpkg.com/@metamask/execution-environments/-/execution-environments-0.10.6.tgz#930ff056e63accf7ab7a59a28bd99064d217599c"
integrity sha512-6ID8vzmIiy418LqRiDKPuDl0RBhHmDMzhgyb65ia6zZNDXKhsBotF/lNMhtHlsAbD9+3XYhi/Nl6YprJxaRisA==
dependencies:
"@metamask/object-multiplex" "^1.2.0"
"@metamask/post-message-stream" "^4.0.0"
"@metamask/providers" "^8.1.1"
"@metamask/snap-types" "^0.10.6"
cross-fetch "^3.1.5"
eth-rpc-errors "^4.0.3"
pump "^3.0.0"
ses "^0.15.7"
stream-browserify "^3.0.0"
"@metamask/forwarder@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@metamask/forwarder/-/forwarder-1.1.0.tgz#13829d8244bbf19ea658c0b20d21a77b67de0bdd"
integrity sha512-Hggj4y0QIjDzKGTXzarhEPIQyFSB2bi2y6YLJNwaT4JmP30UB5Cj6gqoY0M4pj3QT57fzp0BUuGp7F/AUe28tw==
"@metamask/iframe-execution-environment-service@^0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@metamask/iframe-execution-environment-service/-/iframe-execution-environment-service-0.9.0.tgz#721e15ee4651741a599940dbcfa524cc55eaaa47"
integrity sha512-a240sg83sX1dxfBDdRd0uoujaN4V9VtHKELMcTMgpYCI0uE83//Q01a7L8MiBtLhzr8o4D/xXRUIDR0Y9NKc3Q==
"@metamask/iframe-execution-environment-service@^0.10.6":
version "0.10.6"
resolved "https://registry.yarnpkg.com/@metamask/iframe-execution-environment-service/-/iframe-execution-environment-service-0.10.6.tgz#ba7e5499efba2018cef38fff916eaabbfbe1b27b"
integrity sha512-BZbE4+CffNmD1J7Mpvh/evvKcDuP82Jl9panIV0JKzddG8zzOi9xlGtImYf8QWjwN5zx2TyTHAb/WxA+R+nFzw==
dependencies:
"@metamask/controllers" "^25.1.0"
"@metamask/controllers" "^26.0.0"
"@metamask/execution-environments" "^0.10.6"
"@metamask/object-multiplex" "^1.2.0"
"@metamask/post-message-stream" "^4.0.0"
"@metamask/snap-controllers" "^0.9.0"
"@metamask/snap-types" "^0.9.0"
"@metamask/snap-workers" "^0.9.0"
"@metamask/snap-controllers" "^0.10.6"
"@metamask/snap-types" "^0.10.6"
eth-rpc-errors "^4.0.3"
json-rpc-engine "^6.1.0"
json-rpc-middleware-stream "^3.0.0"
@ -2920,13 +2975,15 @@
pump "^3.0.0"
webextension-polyfill-ts "^0.25.0"
"@metamask/rpc-methods@^0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@metamask/rpc-methods/-/rpc-methods-0.9.0.tgz#eb55cc39d2ea9a663211e8d805bdf566af70c764"
integrity sha512-wii0TMuRscet8+x3tqfAcEmY0TrMFzOnD3QFpFVUy3fznv4b/EzDD/XLQToafd2yUaDjUrrS9FHwU9omqzPxcg==
"@metamask/rpc-methods@^0.10.6":
version "0.10.6"
resolved "https://registry.yarnpkg.com/@metamask/rpc-methods/-/rpc-methods-0.10.6.tgz#6e91c2bf33c909e203db438effec6663da4e8692"
integrity sha512-cPqAQExwZGRLqtl22HUBVblr/tIAm/aI3ZMt6QKhwymwU7f/yM+4BOZB1M3+dVlhcIfX1SFFxDW3rFe+MsCksA==
dependencies:
"@metamask/controllers" "^26.0.0"
"@metamask/key-tree" "^3.0.1"
"@metamask/snap-controllers" "^0.9.0"
"@metamask/snap-controllers" "^0.10.6"
"@metamask/types" "^1.1.0"
eth-rpc-errors "^4.0.2"
"@metamask/safe-event-emitter@^2.0.0":
@ -2952,20 +3009,22 @@
isomorphic-fetch "^3.0.0"
lodash "^4.17.21"
"@metamask/snap-controllers@^0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@metamask/snap-controllers/-/snap-controllers-0.9.0.tgz#e0006fc9991e995dd86dff792106990aae2aeda0"
integrity sha512-os3fEai0w4ctpyy6ExlthY8tnww98Vm+RVwOZgrCKDY5dAXqlSXpyWc1uOfkQyiPhUEJtdznJTWzaWzNIO9MfQ==
"@metamask/snap-controllers@^0.10.6":
version "0.10.6"
resolved "https://registry.yarnpkg.com/@metamask/snap-controllers/-/snap-controllers-0.10.6.tgz#72fc52271264c4ae8c986ce4d1a64c34505c7a8b"
integrity sha512-Pa8g1dBVKtjkemRXFUPDQLnP8gcVmQpSgkTMK/m7tVqUinyZX7Gufq5wuApEh1pwRjMQ2R7he/zQn7oZ4qwTJw==
dependencies:
"@metamask/controllers" "^25.1.0"
"@metamask/controllers" "^26.0.0"
"@metamask/execution-environments" "^0.10.6"
"@metamask/object-multiplex" "^1.1.0"
"@metamask/obs-store" "^7.0.0"
"@metamask/post-message-stream" "4.0.0"
"@metamask/safe-event-emitter" "^2.0.0"
"@metamask/snap-workers" "^0.9.0"
"@types/deep-freeze-strict" "^1.1.0"
"@types/semver" "^7.3.9"
ajv "^8.8.2"
concat-stream "^2.0.0"
cross-fetch "^3.1.5"
deep-freeze-strict "^1.1.1"
eth-rpc-errors "^4.0.2"
fast-deep-equal "^3.1.3"
@ -2979,17 +3038,12 @@
semver "^7.3.5"
tar-stream "^2.2.0"
"@metamask/snap-types@^0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@metamask/snap-types/-/snap-types-0.9.0.tgz#aa164111be1b5c53fbaaf03c1bccbdbd0741daa4"
integrity sha512-pK4tvurUhcKMEkTD0XvQze5HCbtrgmpFWDztBekNIMJTXDrnYIEw4Dxn+LwCX7WJ0DN/03brQSEzmIrYbcBw7Q==
"@metamask/snap-types@^0.10.6":
version "0.10.6"
resolved "https://registry.yarnpkg.com/@metamask/snap-types/-/snap-types-0.10.6.tgz#83e3eab797829a9f31906a27fa967a8096e5b704"
integrity sha512-6eSc9hHhC+X133Nw6/WSUy0RWRipdOF3NBKgGFYJxCpvhkY4jGxQ1HAKDUZRzZ3eSewsmQ4hhhIQ+zczj0Subw==
dependencies:
"@metamask/controllers" "^25.1.0"
"@metamask/snap-workers@^0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@metamask/snap-workers/-/snap-workers-0.9.0.tgz#215407b632fef4723dd75af7accf1f02a6a46916"
integrity sha512-+4YY5CQ7OPFPWh4QF5e4COgc0aWL6Df7Oc8/y//Sabp1rmXWI429OzCOlBi+NGJfQ1K7ORBMlRtOwYB9ZmWyLA==
"@metamask/controllers" "^26.0.0"
"@metamask/test-dapp@^5.0.0":
version "5.0.0"
@ -4667,6 +4721,11 @@
dependencies:
"@types/node" "*"
"@types/semver@^7.3.9":
version "7.3.9"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc"
integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==
"@types/source-list-map@*":
version "0.1.2"
resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
@ -8847,12 +8906,12 @@ cross-fetch@^2.1.0:
node-fetch "2.1.2"
whatwg-fetch "2.0.4"
cross-fetch@^3.1.4:
version "3.1.4"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39"
integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==
cross-fetch@^3.1.4, cross-fetch@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
dependencies:
node-fetch "2.6.1"
node-fetch "2.6.7"
cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
@ -19719,22 +19778,17 @@ node-fetch@2.1.2:
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5"
integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=
node-fetch@2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@~2.6.1:
node-fetch@2.6.7, node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@~2.6.1:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
dependencies:
whatwg-url "^5.0.0"
node-forge@^0.7.1, node-forge@^0.7.5, node-forge@^1.0.0, node-forge@^1.2.1, node-forge@~0.7.6:
version "1.2.1"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.2.1.tgz#82794919071ef2eb5c509293325cec8afd0fd53c"
integrity sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==
node-forge@^0.7.1, node-forge@^0.7.5, node-forge@^1.2.1, node-forge@^1.3.0, node-forge@~0.7.6:
version "1.3.0"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.0.tgz#37a874ea723855f37db091e6c186e5b67a01d4b2"
integrity sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==
node-gyp-build@4.3.0, node-gyp-build@^4.2.0, node-gyp-build@^4.2.2, node-gyp-build@^4.2.3, node-gyp-build@^4.3.0:
version "4.3.0"
@ -24567,6 +24621,11 @@ ses@^0.12.4:
"@agoric/make-hardener" "^0.1.2"
"@agoric/transform-module" "^0.4.1"
ses@^0.15.7:
version "0.15.11"
resolved "https://registry.yarnpkg.com/ses/-/ses-0.15.11.tgz#851cb6a20d8967537075d25bb0185051c28c23db"
integrity sha512-lQg6q8/PVf+n18EjP+5Uv1tN9oVQ3br5QxJzPXoAVQleSYnlf20JY9coe7n1B9A6CtIKIHyr6m/TfskcRCufgA==
set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"

Loading…
Cancel
Save