Merge remote-tracking branch 'origin/develop' into master-sync

feature/default_network_editable
ryanml 3 years ago
commit 4901164e5c
  1. 37
      .github/ISSUE_TEMPLATE/bug-report.md
  2. 97
      .github/ISSUE_TEMPLATE/bug-report.yml
  3. 2
      .github/workflows/crowdin_action.yml
  4. 9
      app/_locales/el/messages.json
  5. 74
      app/_locales/en/messages.json
  6. 9
      app/_locales/fr/messages.json
  7. 9
      app/_locales/hi/messages.json
  8. 9
      app/_locales/id/messages.json
  9. 9
      app/_locales/ja/messages.json
  10. 9
      app/_locales/ko/messages.json
  11. 9
      app/_locales/ru/messages.json
  12. 9
      app/_locales/tl/messages.json
  13. 9
      app/_locales/tr/messages.json
  14. 9
      app/_locales/vi/messages.json
  15. 9
      app/_locales/zh_CN/messages.json
  16. 26
      app/images/curve-high.svg
  17. 21
      app/images/curve-low.svg
  18. 17
      app/images/curve-medium.svg
  19. 116
      app/images/videos/recovery-onboarding/subtitles/de.vtt
  20. 4
      app/phishing.html
  21. 24
      app/scripts/controllers/app-state.js
  22. 177
      app/scripts/controllers/metametrics.js
  23. 43
      app/scripts/controllers/metametrics.test.js
  24. 9
      app/scripts/controllers/preferences.js
  25. 281
      app/scripts/controllers/transactions/index.js
  26. 406
      app/scripts/controllers/transactions/index.test.js
  27. 2
      app/scripts/controllers/transactions/tx-state-manager.js
  28. 56
      app/scripts/metamask-controller.js
  29. 12
      development/build/transforms/remove-fenced-code.js
  30. 11
      development/build/transforms/remove-fenced-code.test.js
  31. 4
      development/run-ganache.sh
  32. 8
      lavamoat/browserify/beta/policy.json
  33. 8
      lavamoat/browserify/flask/policy.json
  34. 8
      lavamoat/browserify/main/policy.json
  35. 10
      package.json
  36. 7
      shared/constants/hardware-wallets.js
  37. 40
      shared/constants/metametrics.js
  38. 50
      shared/constants/transaction.js
  39. 145
      test/e2e/fixtures/eip-1559-v2-dapp/state.json
  40. 214
      test/e2e/fixtures/eip-1559-v2/state.json
  41. 40
      test/e2e/fixtures/navigate-transactions/state.json
  42. 214
      test/e2e/fixtures/send-edit-v2/state.json
  43. 13
      test/e2e/fixtures/send-edit/state.json
  44. 16
      test/e2e/ganache.js
  45. 52
      test/e2e/helpers.js
  46. 2
      test/e2e/metamask-ui.spec.js
  47. 4
      test/e2e/metrics.spec.js
  48. 3
      test/e2e/send-eth-with-private-key-test/send-eth-with-private-key.js
  49. 4
      test/e2e/tests/account-details.spec.js
  50. 4
      test/e2e/tests/add-account.spec.js
  51. 6
      test/e2e/tests/add-hide-token.spec.js
  52. 4
      test/e2e/tests/address-book.spec.js
  53. 8
      test/e2e/tests/contract-interactions.spec.js
  54. 4
      test/e2e/tests/custom-rpc-history.spec.js
  55. 277
      test/e2e/tests/edit-gas-fee.spec.js
  56. 19
      test/e2e/tests/from-import-ui.spec.js
  57. 6
      test/e2e/tests/incremental-security.spec.js
  58. 4
      test/e2e/tests/localization.spec.js
  59. 4
      test/e2e/tests/lock-account.spec.js
  60. 4
      test/e2e/tests/lockdown.spec.js
  61. 6
      test/e2e/tests/metamask-responsive-ui.spec.js
  62. 4
      test/e2e/tests/navigate-transactions.spec.js
  63. 4
      test/e2e/tests/permissions.spec.js
  64. 4
      test/e2e/tests/personal-sign.spec.js
  65. 4
      test/e2e/tests/provider-api.spec.js
  66. 113
      test/e2e/tests/send-edit.spec.js
  67. 12
      test/e2e/tests/send-eth.spec.js
  68. 4
      test/e2e/tests/signature-request.spec.js
  69. 4
      test/e2e/tests/simple-send.spec.js
  70. 4
      test/e2e/tests/threebox.spec.js
  71. 8
      test/stub/provider.js
  72. 32
      ui/components/app/account-menu/account-menu.component.js
  73. 2
      ui/components/app/account-menu/account-menu.test.js
  74. 4
      ui/components/app/account-menu/index.scss
  75. 48
      ui/components/app/account-menu/keyring-label.js
  76. 62
      ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-defaults/advanced-gas-fee-defaults.js
  77. 19
      ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-defaults/advanced-gas-fee-defaults.test.js
  78. 4
      ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-defaults/index.scss
  79. 2
      ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-gas-limit/advanced-gas-fee-gas-limit.js
  80. 19
      ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-inputs/base-fee-input/base-fee-input.js
  81. 1
      ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-inputs/priority-fee-input/priority-fee-input.js
  82. 13
      ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-inputs/utils.js
  83. 7
      ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-popover.js
  84. 2
      ui/components/app/app-components.scss
  85. 30
      ui/components/app/asset-list-item/asset-list-item.js
  86. 2
      ui/components/app/cancel-speedup-popover/cancel-speedup-popover.js
  87. 129
      ui/components/app/collectible-details/collectible-details.js
  88. 36
      ui/components/app/collectible-details/index.scss
  89. 2
      ui/components/app/collectibles-detection-notice/collectibles-detection-notice.js
  90. 2
      ui/components/app/collectibles-detection-notice/index.scss
  91. 138
      ui/components/app/collectibles-items/collectibles-items.js
  92. 6
      ui/components/app/collectibles-items/index.scss
  93. 48
      ui/components/app/collectibles-tab/collectibles-tab.js
  94. 6
      ui/components/app/collectibles-tab/collectibles-tab.test.js
  95. 2
      ui/components/app/collectibles-tab/index.scss
  96. 3
      ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.test.js
  97. 2
      ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
  98. 2
      ui/components/app/confirm-page-container/confirm-page-container.component.js
  99. 94
      ui/components/app/confirm-page-container/enableEIP1559V2-notice/enableEIP1559V2-notice.js
  100. 1
      ui/components/app/confirm-page-container/enableEIP1559V2-notice/index.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,37 +0,0 @@
---
name: Bug Report
about: Using MetaMask, but it's not working as you expect?
---
<!--
BEFORE SUBMITTING:
1) Please search to make sure this issue has not been opened already
2) If this is an implementation question or trouble with your personal project, please post on StackExchange. This will get your question answered more quickly and make it easier for other devs to find the answer in the future.
-->
**Describe the bug**
A clear and concise description of what the bug is.
**Steps to reproduce (REQUIRED)**
Steps to reproduce the behavior, libraries used with version number, and/or any setup information to easily reproduce:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Browser details (please complete the following information):**
- OS: [e.g. OS X, Windows]
- Hardware Wallet: [e.g. Trezor Firmware version 1.8.3, Ledger Nano S Firmware version 1.6.0]
- Browser: [e.g. Chrome Version 79.0.3945.79 (Official Build) (64-bit), Firefox Browser 71.0 (64-bit)]
- MetaMask Version: [e.g. 5.0.2 - find it in Settings > About]
**Additional context (Error Messages, etc.)**
Add any other context about the problem here.

@ -0,0 +1,97 @@
name: Bug Report
description: Using MetaMask, but it's not working as you expect?
title: "[Bug]: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
## **Before Submitting:**
* Please search to make sure this issue has not been opened already.
* If this is a question about how to integrate MetaMask with your project, please ask in our [Community forum](https://community.metamask.io/c/developer-questions/) instead. This will get your question answered more quickly and make it easier for other devs to find the answer in the future.
- type: textarea
id: what-happened
attributes:
label: Describe the bug
description: What happened? What did you expect to happen? Please include screenshots if applicable!
placeholder: Tell us what you see!
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Steps to reproduce
description: List all steps needed to reproduce the problem
value: |
1.
validations:
required: true
- type: textarea
id: error
attributes:
label: Error messages or log output
description: Please copy and paste any relevant error messages or log output. This will be automatically formatted, so there is no need for backticks.
render: shell
- type: input
id: version
attributes:
label: Version
description: What version of MetaMask are you running? You can find the version in "Settings" > "About"
validations:
required: true
- type: dropdown
id: build
attributes:
label: Build type
description: Are you using a testing or development build of MetaMask? If so, please select the type of build you are using.
options:
- Beta
- Flask
- Other (please specify exactly where you obtained this build in "Additional Context" section)
- type: dropdown
id: browsers
attributes:
label: Browser
description: Which browsers have you seen the problem on?
multiple: true
options:
- Chrome
- Firefox
- Microsoft Edge
- Brave
- Other (please elaborate in the "Additional Context" section)
validations:
required: true
- type: dropdown
id: os
attributes:
label: Operating system
description: Which operating systems have you seen the problem on?
multiple: true
options:
- Windows
- MacOS
- Linux
- Other (please elaborate in the "Additional Context" section)
validations:
required: true
- type: dropdown
id: hardware-wallet
attributes:
label: Hardware wallet
description: Are you using any of these hardware wallets? Please include the firmware version in the "Additional context" section below for any that you select here.
multiple: true
options:
- Ledger
- Trezor
- Keystone
- GridPlus Lattice1
- Other (please elaborate in the "Additional Context" section)
- type: textarea
id: additional
attributes:
label: Additional context
description: Add any other context about the problem here, e.g. related issues, additional error messages or logs, or any potentially relevant details about the environment or situation the bug occurred in.

@ -26,6 +26,6 @@ jobs:
upload_translations: true
download_translations: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.METAMASKBOT_CROWDIN_TOKEN }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}

@ -140,9 +140,6 @@
"addMemo": {
"message": "Προσθήκη σημειώματος"
},
"addNFT": {
"message": "Προσθήκη NFT"
},
"addNetwork": {
"message": "Προσθήκη Δικτύου"
},
@ -1296,9 +1293,6 @@
"high": {
"message": "Επιθετική"
},
"highGasSettingToolTipDialog": {
"message": "Υψηλή πιθανότητα, ακόμη και σε ευμετάβλητες αγορές"
},
"highGasSettingToolTipMessage": {
"message": "Χρησιμοποιήστε $1 για να καλύψετε απότομες αυξήσεις της κίνησης του δικτύου λόγω των δημοφιλών ξεκινημάτων NFT.",
"description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
@ -1309,9 +1303,6 @@
"history": {
"message": "Ιστορικό"
},
"id": {
"message": "Αναγνωριστικό"
},
"import": {
"message": "Εισαγωγή",
"description": "Button to import an account from a selected file"

@ -140,9 +140,6 @@
"addMemo": {
"message": "Add memo"
},
"addNFT": {
"message": "Add NFT"
},
"addNetwork": {
"message": "Add Network"
},
@ -458,6 +455,10 @@
"close": {
"message": "Close"
},
"collectibleAddressError": {
"message": "This token is an NFT. Add on the $1",
"description": "$1 is a clickable link with text defined by the 'importNFTPage' key"
},
"confirm": {
"message": "Confirm"
},
@ -595,6 +596,9 @@
"contractInteraction": {
"message": "Contract Interaction"
},
"convertTokenToNFTDescription": {
"message": "We've detected that this asset is an NFT. Metamask now has full native support for NFTs. Would you like to remove it from your token list and add it as an NFT?"
},
"copiedExclamation": {
"message": "Copied!"
},
@ -652,6 +656,15 @@
"currentlyUnavailable": {
"message": "Unavailable on this network"
},
"curveHighGasEstimate": {
"message": "Aggressive gas estimate graph"
},
"curveLowGasEstimate": {
"message": "Low gas estimate graph"
},
"curveMediumGasEstimate": {
"message": "Market gas estimate graph"
},
"custom": {
"message": "Advanced"
},
@ -659,7 +672,7 @@
"message": "Customize Gas"
},
"customGasSettingToolTipMessage": {
"message": "Use $1 to customise the gas price. This can be confusing if you aren’t familiar. Interact at your own risk.",
"message": "Use $1 to customize the gas price. This can be confusing if you aren’t familiar. Interact at your own risk.",
"description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold fontweight"
},
"customGasSubTitle": {
@ -933,6 +946,22 @@
"enableAutoDetect": {
"message": " Enable Autodetect"
},
"enableEIP1559V2": {
"message": "Enable Enhanced Gas Fee UI"
},
"enableEIP1559V2AlertMessage": {
"message": "We've updated how gas fee estimation and customization works."
},
"enableEIP1559V2ButtonText": {
"message": "Turn on Enhanced Gas Fee UI in Settings"
},
"enableEIP1559V2Description": {
"message": "We've updated how gas estimation and customization works. Turn on if you'd like to use the new gas experience. $1",
"description": "$1 here is Learn More link"
},
"enableEIP1559V2Header": {
"message": "New gas experience"
},
"enableFromSettings": {
"message": " Enable it from Settings."
},
@ -1190,6 +1219,9 @@
"gasLimitInfoTooltipContent": {
"message": "Gas limit is the maximum amount of units of gas you are willing to spend."
},
"gasLimitRecommended": {
"message": "Recommended gas limit is $1. If the gas limit is less than that, it may fail."
},
"gasLimitTooLow": {
"message": "Gas limit must be at least 21000"
},
@ -1337,11 +1369,8 @@
"high": {
"message": "Aggressive"
},
"highGasSettingToolTipDialog": {
"message": "High probability, even in volatile markets"
},
"highGasSettingToolTipMessage": {
"message": "Use $1 to cover surges in network traffic due to things like popular NFT drops.",
"message": "High probability, even in volatile markets. Use $1 to cover surges in network traffic due to things like popular NFT drops.",
"description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
},
"highLowercase": {
@ -1350,9 +1379,6 @@
"history": {
"message": "History"
},
"id": {
"message": "ID"
},
"import": {
"message": "Import",
"description": "Button to import an account from a selected file"
@ -1386,6 +1412,18 @@
"importMyWallet": {
"message": "Import My Wallet"
},
"importNFT": {
"message": "Import NFT"
},
"importNFTAddressToolTip": {
"message": "On OpenSea, for example, on the NFT's page under Details, there is a blue hyperlinked value labeled 'Contract Address'. If you click on this, it will take you to the contract's address on Etherscan; at the top-left of that page, there should be an icon labeled 'Contract', and to the right, a long string of letters and numbers. This is the address of the contract that created your NFT. Click on the 'copy' icon to the right of the address, and you'll have it on your clipboard."
},
"importNFTPage": {
"message": "Import NFT page"
},
"importNFTTokenIdToolTip": {
"message": "A collectible's ID is a unique identifier since no two NFTs are alike. Again, on OpenSea this number is under 'Details'. Make a note of it, or copy it onto your clipboard."
},
"importNFTs": {
"message": "Import NFTs"
},
@ -1439,6 +1477,9 @@
"invalidAddressRecipientNotEthNetwork": {
"message": "Not ETH network, set to lowercase"
},
"invalidAssetType": {
"message": "This asset is an NFT and needs to be re-added on the Import NFTs page found under the NFTs tab"
},
"invalidBlockExplorerURL": {
"message": "Invalid Block Explorer URL"
},
@ -1619,7 +1660,7 @@
"message": "low"
},
"lowPriorityMessage": {
"message": "Future transactions will queue after this one. This price was last seen was some time ago."
"message": "Future transactions will queue after this one."
},
"mainnet": {
"message": "Ethereum Mainnet"
@ -1891,7 +1932,7 @@
"description": "The next nonce according to MetaMask's internal logic"
},
"nftTokenIdPlaceholder": {
"message": "Enter the collectible ID"
"message": "Enter the Token ID"
},
"nfts": {
"message": "NFTs"
@ -2155,7 +2196,7 @@
"message": "Passwords Don't Match"
},
"pastePrivateKey": {
"message": "Paste your private key string here:",
"message": "Enter your private key string here:",
"description": "For importing an account from a private key"
},
"pending": {
@ -2467,7 +2508,7 @@
"message": "Separate each word with a single space"
},
"seedPhrasePlaceholderPaste": {
"message": "Paste Secret Recovery Phrase from clipboard"
"message": "Enter your Secret Recovery Phrase"
},
"seedPhraseReq": {
"message": "Secret Recovery Phrases contain 12, 15, 18, 21, or 24 words"
@ -3508,6 +3549,9 @@
"message": "$1 of $2 pending",
"description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total"
},
"yes": {
"message": "Yes"
},
"yesLetsTry": {
"message": "Yes, let's try"
},

@ -140,9 +140,6 @@
"addMemo": {
"message": "Ajouter un mémo"
},
"addNFT": {
"message": "Ajouter un NFT"
},
"addNetwork": {
"message": "Ajouter un réseau"
},
@ -1296,9 +1293,6 @@
"high": {
"message": "Agressif"
},
"highGasSettingToolTipDialog": {
"message": "Probabilité élevée, même sur des marchés volatils"
},
"highGasSettingToolTipMessage": {
"message": "Utilisez $1 pour couvrir les envolées du trafic réseau dues à des événements tels que les chutes de NFT populaires.",
"description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
@ -1309,9 +1303,6 @@
"history": {
"message": "Historique"
},
"id": {
"message": "ID"
},
"import": {
"message": "Importer",
"description": "Button to import an account from a selected file"

@ -140,9 +140,6 @@
"addMemo": {
"message": "म"
},
"addNFT": {
"message": "NFT ज"
},
"addNetwork": {
"message": "नटवरक ज"
},
@ -1296,9 +1293,6 @@
"high": {
"message": "आकमक"
},
"highGasSettingToolTipDialog": {
"message": "उचच सवन, असिर ब"
},
"highGasSettingToolTipMessage": {
"message": "लकपिय NFT डप ज वजह सटवरक टिक मि कवर करनिए $1 क उपयग कर।",
"description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
@ -1309,9 +1303,6 @@
"history": {
"message": "इतिस"
},
"id": {
"message": "ID"
},
"import": {
"message": "आयत कर",
"description": "Button to import an account from a selected file"

@ -140,9 +140,6 @@
"addMemo": {
"message": "Tambahkan memo"
},
"addNFT": {
"message": "Tambahkan NFT"
},
"addNetwork": {
"message": "Tambahkan Jaringan"
},
@ -1296,9 +1293,6 @@
"high": {
"message": "Agresif"
},
"highGasSettingToolTipDialog": {
"message": "Probabilitas tinggi, bahkan di pasar yang tidak stabil"
},
"highGasSettingToolTipMessage": {
"message": "Gunakan $1 untuk menutupi lonjakan lalu lintas jaringan karena hal-hal seperti penurunan NFT populer.",
"description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
@ -1309,9 +1303,6 @@
"history": {
"message": "Riwayat"
},
"id": {
"message": "ID"
},
"import": {
"message": "Impor",
"description": "Button to import an account from a selected file"

@ -140,9 +140,6 @@
"addMemo": {
"message": "メモを追加"
},
"addNFT": {
"message": "NFTを追加"
},
"addNetwork": {
"message": "ネットワークの追加"
},
@ -1296,9 +1293,6 @@
"high": {
"message": "積極的"
},
"highGasSettingToolTipDialog": {
"message": "変動の激しい市場においても高い確率"
},
"highGasSettingToolTipMessage": {
"message": "人気のNFTドロップなどによるネットワークトラフィックの急増に備えるため、$1を使用してください。",
"description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
@ -1309,9 +1303,6 @@
"history": {
"message": "履歴"
},
"id": {
"message": "ID"
},
"import": {
"message": "インポート",
"description": "Button to import an account from a selected file"

@ -140,9 +140,6 @@
"addMemo": {
"message": "메모 추가"
},
"addNFT": {
"message": "NFT 추가"
},
"addNetwork": {
"message": "네트워크 추가"
},
@ -1296,9 +1293,6 @@
"high": {
"message": "공격적"
},
"highGasSettingToolTipDialog": {
"message": "변동성이 큰 시장에서도 높은 확률"
},
"highGasSettingToolTipMessage": {
"message": "인기 있는 NFT의 하락 등으로 인한 네트워크 트래픽 급증을 커버하려면 $1을 사용하세요.",
"description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
@ -1309,9 +1303,6 @@
"history": {
"message": "기록"
},
"id": {
"message": "ID"
},
"import": {
"message": "가져오기",
"description": "Button to import an account from a selected file"

@ -140,9 +140,6 @@
"addMemo": {
"message": "Добавить примечание"
},
"addNFT": {
"message": "Добавить NFT"
},
"addNetwork": {
"message": "Добавить сеть"
},
@ -1296,9 +1293,6 @@
"high": {
"message": "Агрессивный"
},
"highGasSettingToolTipDialog": {
"message": "Высокая вероятность даже на волатильных рынках"
},
"highGasSettingToolTipMessage": {
"message": "Используйте $1, чтобы компенсировать скачки сетевого трафика из-за таких событий, как дропы популярных NFT.",
"description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
@ -1309,9 +1303,6 @@
"history": {
"message": "История"
},
"id": {
"message": "Ид."
},
"import": {
"message": "Импорт",
"description": "Button to import an account from a selected file"

@ -140,9 +140,6 @@
"addMemo": {
"message": "Magdagdag ng memo"
},
"addNFT": {
"message": "Magdagdag ng NFT"
},
"addNetwork": {
"message": "Magdagdag ng Network"
},
@ -1296,9 +1293,6 @@
"high": {
"message": "Agresibo"
},
"highGasSettingToolTipDialog": {
"message": "Mataas na probabilidad, kahit sa mga pabagu-bagong market"
},
"highGasSettingToolTipMessage": {
"message": "Gamitin ang $1 upang pagtakpan ang mga surge sa network traffic dahil sa mga bagay tulad ng popular na pagbagsak ng NFT.",
"description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
@ -1309,9 +1303,6 @@
"history": {
"message": "History"
},
"id": {
"message": "ID"
},
"import": {
"message": "Mag-import",
"description": "Button to import an account from a selected file"

@ -140,9 +140,6 @@
"addMemo": {
"message": "Not ekleyin"
},
"addNFT": {
"message": "NFT ekleyin"
},
"addNetwork": {
"message": "Ağ ekleyin"
},
@ -1296,9 +1293,6 @@
"high": {
"message": "Agresif"
},
"highGasSettingToolTipDialog": {
"message": "Dalgalı piyasalarda dahi yüksek olasılık"
},
"highGasSettingToolTipMessage": {
"message": "Popüler NFT düşüşleri gibi şeyler nedeniyle ağ trafiğindeki dalgalanmaları kapsayacak şekilde $1 kullanın.",
"description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
@ -1309,9 +1303,6 @@
"history": {
"message": "Geçmiş"
},
"id": {
"message": "Kimlik"
},
"import": {
"message": "Al",
"description": "Button to import an account from a selected file"

@ -140,9 +140,6 @@
"addMemo": {
"message": "Thêm bản ghi nhớ"
},
"addNFT": {
"message": "Thêm NFT"
},
"addNetwork": {
"message": "Thêm mạng"
},
@ -1296,9 +1293,6 @@
"high": {
"message": "Linh hoạt"
},
"highGasSettingToolTipDialog": {
"message": "Có khả năng cao, ngay cả trong thị trường biến động"
},
"highGasSettingToolTipMessage": {
"message": "Sử dụng $1 để bù đắp khi lưu lượng mạng lưới tăng vọt trong những trường hợp như phát hành NFT nổi tiếng.",
"description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
@ -1309,9 +1303,6 @@
"history": {
"message": "Lịch sử"
},
"id": {
"message": "ID"
},
"import": {
"message": "Nhập",
"description": "Button to import an account from a selected file"

@ -140,9 +140,6 @@
"addMemo": {
"message": "添加备忘录"
},
"addNFT": {
"message": "添加NFT"
},
"addNetwork": {
"message": "添加网络"
},
@ -1296,9 +1293,6 @@
"high": {
"message": "进取"
},
"highGasSettingToolTipDialog": {
"message": "高概率,即使在不稳定的市场中也是如此。"
},
"highGasSettingToolTipMessage": {
"message": "使用$1来覆盖网络流量因像流行的 NFT 丢弃而出现的剧增。",
"description": "$1 is key 'high' (text: 'Aggressive') separated here so that it can be passed in with bold fontweight"
@ -1309,9 +1303,6 @@
"history": {
"message": "历史记录"
},
"id": {
"message": "ID"
},
"import": {
"message": "导入",
"description": "Button to import an account from a selected file"

@ -1 +1,25 @@
<svg width="136" height="31" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M91.201 8.705h1.03l2.39 5.65 2.4-5.65h1.01l-3.02 7.1h-.78l-3.03-7.1Zm9.327 7.2a2.73 2.73 0 0 1-1.06-.2 2.431 2.431 0 0 1-1.35-1.37 2.91 2.91 0 0 1-.18-1.03c0-.367.063-.707.19-1.02.126-.32.303-.597.53-.83.233-.234.51-.417.83-.55.32-.134.67-.2 1.05-.2.326 0 .633.056.92.17.286.106.536.27.75.49.22.213.393.476.52.79.126.306.19.66.19 1.06v.13c0 .033-.004.076-.01.13h-4.1c.006.233.053.45.14.65.093.2.213.373.36.52.153.146.333.263.54.35.213.08.443.12.69.12.386 0 .703-.077.95-.23a1.82 1.82 0 0 0 .61-.64l.68.47a2.48 2.48 0 0 1-.91.87c-.374.213-.82.32-1.34.32Zm1.51-3.13a1.601 1.601 0 0 0-.19-.55c-.087-.16-.2-.297-.34-.41a1.431 1.431 0 0 0-.46-.26 1.645 1.645 0 0 0-.54-.09 1.73 1.73 0 0 0-1.04.35 1.5 1.5 0 0 0-.38.41c-.107.16-.18.343-.22.55h3.17Zm2.1-1.97h.86v.97c.047-.16.12-.304.22-.43a1.556 1.556 0 0 1 .74-.52c.147-.047.294-.07.44-.07.134 0 .264.013.39.04v.89a.78.78 0 0 0-.23-.06 1.342 1.342 0 0 0-.24-.02c-.16 0-.32.036-.48.11-.153.066-.293.17-.42.31-.12.14-.22.32-.3.54-.08.213-.12.466-.12.76v2.48h-.86v-5Zm4.032 7.09 1.09-2.35-2.19-4.74h.95l1.72 3.8 1.71-3.8h.96l-3.28 7.09h-.96Zm7.287-2.09v-7.5h.86v3.27c.173-.3.4-.52.68-.66s.583-.21.91-.21c.28 0 .536.046.77.14.233.093.43.23.59.41.166.173.296.386.39.64.093.246.14.523.14.83v3.08h-.85v-2.95c0-.42-.11-.75-.33-.99-.214-.247-.497-.37-.85-.37-.2 0-.39.04-.57.12-.174.08-.327.2-.46.36-.127.153-.23.343-.31.57-.074.226-.11.486-.11.78v2.48h-.86Zm6.328-6.34a.599.599 0 0 1-.62-.61c0-.167.06-.31.18-.43s.267-.18.44-.18a.602.602 0 0 1 .6.61c0 .173-.056.32-.17.44a.581.581 0 0 1-.43.17Zm-.44 1.34h.86v5h-.86v-5Zm4.671 4.19c.246 0 .473-.044.68-.13.213-.087.393-.207.54-.36.146-.16.26-.347.34-.56.086-.214.13-.447.13-.7 0-.247-.044-.477-.13-.69a1.588 1.588 0 0 0-.34-.56 1.542 1.542 0 0 0-.54-.36 1.637 1.637 0 0 0-.68-.14c-.254 0-.484.046-.69.14a1.548 1.548 0 0 0-.53.36c-.147.153-.264.34-.35.56-.08.213-.12.443-.12.69 0 .253.04.486.12.7.086.213.203.4.35.56.146.153.323.273.53.36.206.086.436.13.69.13Zm-.05 3c-.54 0-1.014-.1-1.42-.3-.407-.2-.717-.44-.93-.72l.6-.6c.2.253.443.456.73.61.293.153.633.23 1.02.23.213 0 .42-.034.62-.1.2-.067.376-.174.53-.32.16-.14.286-.32.38-.54.093-.214.14-.47.14-.77v-.57a2.032 2.032 0 0 1-.72.63c-.307.166-.647.25-1.02.25-.347 0-.67-.064-.97-.19-.3-.134-.56-.314-.78-.54a2.623 2.623 0 0 1-.52-.81 2.8 2.8 0 0 1-.18-1.01c0-.354.06-.684.18-.99.126-.314.3-.584.52-.81.22-.234.48-.414.78-.54.3-.134.623-.2.97-.2.373 0 .713.083 1.02.25.306.16.546.366.72.62v-.77h.86v4.71c0 .42-.067.783-.2 1.09-.127.313-.304.57-.53.77-.227.206-.494.36-.8.46-.307.106-.64.16-1 .16Zm4.149-2.19v-7.5h.86v3.27c.173-.3.4-.52.68-.66s.583-.21.91-.21c.28 0 .537.046.77.14.233.093.43.23.59.41.167.173.297.386.39.64.093.246.14.523.14.83v3.08h-.85v-2.95c0-.42-.11-.75-.33-.99-.213-.247-.497-.37-.85-.37-.2 0-.39.04-.57.12-.173.08-.327.2-.46.36-.127.153-.23.343-.31.57-.073.226-.11.486-.11.78v2.48h-.86Z" fill="#F66A0A"/><path opacity=".3" d="M19.506 22.805c-10.763 7.42-19.416 8-19.416 8h110.25s-8.653-.58-19.417-8c-10.763-7.42-23.363-22-35.708-22-12.345 0-24.945 14.58-35.709 22Z" fill="#037DD6"/><mask id="a" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="111" height="31"><path d="M19.506 22.672c-10.763 7.42-19.416 8-19.416 8h110.25s-8.653-.58-19.417-8c-10.763-7.42-23.363-22-35.708-22-12.345 0-24.945 14.58-35.709 22Z" fill="#EAF6FF"/></mask><g mask="url(#a)"><path fill="#F66A0A" stroke="#fff" stroke-width="2" d="M91.986-5.143h20.706v39.25H91.986z"/></g></svg>
<svg width="162" height="32" viewBox="0 0 162 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2140_17920)">
<rect width="161" height="31" transform="translate(0.8125 0.0820312)" fill="white"/>
<path d="M117.469 8.63798H118.499L120.889 14.288L123.289 8.63798H124.299L121.279 15.738H120.499L117.469 8.63798Z" fill="#F66A0A"/>
<path d="M126.795 15.838C126.409 15.838 126.055 15.7713 125.735 15.638C125.415 15.5046 125.142 15.3213 124.915 15.088C124.689 14.8546 124.512 14.5813 124.385 14.268C124.265 13.948 124.205 13.6046 124.205 13.238C124.205 12.8713 124.269 12.5313 124.395 12.218C124.522 11.898 124.699 11.6213 124.925 11.388C125.159 11.1546 125.435 10.9713 125.755 10.838C126.075 10.7046 126.425 10.638 126.805 10.638C127.132 10.638 127.439 10.6946 127.725 10.808C128.012 10.9146 128.262 11.078 128.475 11.298C128.695 11.5113 128.869 11.7746 128.995 12.088C129.122 12.3946 129.185 12.748 129.185 13.148C129.185 13.1946 129.185 13.238 129.185 13.278C129.185 13.3113 129.182 13.3546 129.175 13.408H125.075C125.082 13.6413 125.129 13.858 125.215 14.058C125.309 14.258 125.429 14.4313 125.575 14.578C125.729 14.7246 125.909 14.8413 126.115 14.928C126.329 15.008 126.559 15.048 126.805 15.048C127.192 15.048 127.509 14.9713 127.755 14.818C128.009 14.658 128.212 14.4446 128.365 14.178L129.045 14.648C128.819 15.0146 128.515 15.3046 128.135 15.518C127.762 15.7313 127.315 15.838 126.795 15.838ZM128.305 12.708C128.272 12.5013 128.209 12.318 128.115 12.158C128.029 11.998 127.915 11.8613 127.775 11.748C127.642 11.6346 127.489 11.548 127.315 11.488C127.142 11.428 126.962 11.398 126.775 11.398C126.589 11.398 126.405 11.428 126.225 11.488C126.045 11.548 125.882 11.6346 125.735 11.748C125.589 11.8546 125.462 11.9913 125.355 12.158C125.249 12.318 125.175 12.5013 125.135 12.708H128.305Z" fill="#F66A0A"/>
<path d="M130.406 10.738H131.266V11.708C131.312 11.548 131.386 11.4046 131.486 11.278C131.586 11.1513 131.699 11.0446 131.826 10.958C131.952 10.8713 132.086 10.8046 132.226 10.758C132.372 10.7113 132.519 10.688 132.666 10.688C132.799 10.688 132.929 10.7013 133.056 10.728V11.618C132.976 11.5846 132.899 11.5646 132.826 11.558C132.752 11.5446 132.672 11.538 132.586 11.538C132.426 11.538 132.266 11.5746 132.106 11.648C131.952 11.7146 131.812 11.818 131.686 11.958C131.566 12.098 131.466 12.278 131.386 12.498C131.306 12.7113 131.266 12.9646 131.266 13.258V15.738H130.406V10.738Z" fill="#F66A0A"/>
<path d="M134.438 17.828L135.528 15.478L133.338 10.738H134.288L136.008 14.538L137.718 10.738H138.678L135.398 17.828H134.438Z" fill="#F66A0A"/>
<path d="M141.724 15.738V8.23798H142.584V11.508C142.757 11.208 142.984 10.988 143.264 10.848C143.544 10.708 143.847 10.638 144.174 10.638C144.454 10.638 144.711 10.6846 144.944 10.778C145.177 10.8713 145.374 11.008 145.534 11.188C145.701 11.3613 145.831 11.5746 145.924 11.828C146.017 12.0746 146.064 12.3513 146.064 12.658V15.738H145.214V12.788C145.214 12.368 145.104 12.038 144.884 11.798C144.671 11.5513 144.387 11.428 144.034 11.428C143.834 11.428 143.644 11.468 143.464 11.548C143.291 11.628 143.137 11.748 143.004 11.908C142.877 12.0613 142.774 12.2513 142.694 12.478C142.621 12.7046 142.584 12.9646 142.584 13.258V15.738H141.724Z" fill="#F66A0A"/>
<path d="M148.053 9.39798C147.879 9.39798 147.733 9.34131 147.613 9.22798C147.493 9.10798 147.433 8.96131 147.433 8.78798C147.433 8.62131 147.493 8.47798 147.613 8.35798C147.733 8.23798 147.879 8.17798 148.053 8.17798C148.226 8.17798 148.369 8.23798 148.483 8.35798C148.596 8.47798 148.653 8.62131 148.653 8.78798C148.653 8.96131 148.596 9.10798 148.483 9.22798C148.369 9.34131 148.226 9.39798 148.053 9.39798ZM147.613 10.738H148.473V15.738H147.613V10.738Z" fill="#F66A0A"/>
<path d="M152.283 14.928C152.53 14.928 152.756 14.8846 152.963 14.798C153.176 14.7113 153.356 14.5913 153.503 14.438C153.65 14.278 153.763 14.0913 153.843 13.878C153.93 13.6646 153.973 13.4313 153.973 13.178C153.973 12.9313 153.93 12.7013 153.843 12.488C153.763 12.268 153.65 12.0813 153.503 11.928C153.356 11.7746 153.176 11.6546 152.963 11.568C152.756 11.4746 152.53 11.428 152.283 11.428C152.03 11.428 151.8 11.4746 151.593 11.568C151.386 11.6546 151.21 11.7746 151.063 11.928C150.916 12.0813 150.8 12.268 150.713 12.488C150.633 12.7013 150.593 12.9313 150.593 13.178C150.593 13.4313 150.633 13.6646 150.713 13.878C150.8 14.0913 150.916 14.278 151.063 14.438C151.21 14.5913 151.386 14.7113 151.593 14.798C151.8 14.8846 152.03 14.928 152.283 14.928ZM152.233 17.928C151.693 17.928 151.22 17.828 150.813 17.628C150.406 17.428 150.096 17.188 149.883 16.908L150.483 16.308C150.683 16.5613 150.926 16.7646 151.213 16.918C151.506 17.0713 151.846 17.148 152.233 17.148C152.446 17.148 152.653 17.1146 152.853 17.048C153.053 16.9813 153.23 16.8746 153.383 16.728C153.543 16.588 153.67 16.408 153.763 16.188C153.856 15.9746 153.903 15.718 153.903 15.418V14.848C153.73 15.1013 153.49 15.3113 153.183 15.478C152.876 15.6446 152.536 15.728 152.163 15.728C151.816 15.728 151.493 15.6646 151.193 15.538C150.893 15.4046 150.633 15.2246 150.413 14.998C150.193 14.7646 150.02 14.4946 149.893 14.188C149.773 13.8746 149.713 13.538 149.713 13.178C149.713 12.8246 149.773 12.4946 149.893 12.188C150.02 11.8746 150.193 11.6046 150.413 11.378C150.633 11.1446 150.893 10.9646 151.193 10.838C151.493 10.7046 151.816 10.638 152.163 10.638C152.536 10.638 152.876 10.7213 153.183 10.888C153.49 11.048 153.73 11.2546 153.903 11.508V10.738H154.763V15.448C154.763 15.868 154.696 16.2313 154.563 16.538C154.436 16.8513 154.26 17.108 154.033 17.308C153.806 17.5146 153.54 17.668 153.233 17.768C152.926 17.8746 152.593 17.928 152.233 17.928Z" fill="#F66A0A"/>
<path d="M156.382 15.738V8.23798H157.242V11.508C157.416 11.208 157.642 10.988 157.922 10.848C158.202 10.708 158.506 10.638 158.832 10.638C159.112 10.638 159.369 10.6846 159.602 10.778C159.836 10.8713 160.032 11.008 160.192 11.188C160.359 11.3613 160.489 11.5746 160.582 11.828C160.676 12.0746 160.722 12.3513 160.722 12.658V15.738H159.872V12.788C159.872 12.368 159.762 12.038 159.542 11.798C159.329 11.5513 159.046 11.428 158.692 11.428C158.492 11.428 158.302 11.468 158.122 11.548C157.949 11.628 157.796 11.748 157.662 11.908C157.536 12.0613 157.432 12.2513 157.352 12.478C157.279 12.7046 157.242 12.9646 157.242 13.258V15.738H156.382Z" fill="#F66A0A"/>
<path opacity="0.3" d="M45.774 22.738C35.0101 30.158 26.3574 30.738 26.3574 30.738H136.608C136.608 30.738 127.955 30.158 117.191 22.738C106.427 15.3181 93.8273 0.738037 81.4825 0.738037C69.1377 0.738037 56.5378 15.3181 45.774 22.738Z" fill="#037DD6"/>
<mask id="mask0_2140_17920" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="26" y="0" width="111" height="31">
<path d="M45.774 22.6052C35.0101 30.0252 26.3574 30.6052 26.3574 30.6052H136.608C136.608 30.6052 127.955 30.0252 117.191 22.6052C106.427 15.1853 93.8273 0.605225 81.4825 0.605225C69.1377 0.605225 56.5378 15.1853 45.774 22.6052Z" fill="#EAF6FF"/>
</mask>
<g mask="url(#mask0_2140_17920)">
<rect x="118.254" y="-5.20923" width="20.7061" height="39.25" fill="#F66A0A" stroke="white" stroke-width="2"/>
</g>
</g>
<defs>
<clipPath id="clip0_2140_17920">
<rect width="161" height="31" fill="white" transform="translate(0.8125 0.0820312)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

@ -1 +1,20 @@
<svg width="125" height="31" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14.233 8.738h.93v6.22h3.43v.88h-4.36v-7.1Zm7.412 7.2c-.38 0-.73-.067-1.05-.2a2.679 2.679 0 0 1-.83-.56 2.569 2.569 0 0 1-.55-.82c-.126-.32-.19-.66-.19-1.02s.064-.697.19-1.01a2.54 2.54 0 0 1 .55-.83c.234-.233.51-.417.83-.55.32-.14.67-.21 1.05-.21.374 0 .72.07 1.04.21.32.133.597.317.83.55.234.233.414.51.54.83.134.313.2.65.2 1.01s-.066.7-.2 1.02a2.44 2.44 0 0 1-.54.82 2.68 2.68 0 0 1-.83.56c-.32.133-.666.2-1.04.2Zm0-.8c.26 0 .497-.047.71-.14.214-.093.394-.22.54-.38.154-.167.27-.357.35-.57.087-.22.13-.457.13-.71 0-.247-.043-.48-.13-.7-.08-.22-.196-.41-.35-.57a1.52 1.52 0 0 0-.54-.39 1.754 1.754 0 0 0-.71-.14c-.26 0-.496.047-.71.14a1.619 1.619 0 0 0-.55.39c-.153.16-.273.35-.36.57-.08.22-.12.453-.12.7 0 .253.04.49.12.71.087.213.207.403.36.57.154.16.337.287.55.38.214.093.45.14.71.14Zm6.825-2.89-1.23 3.59h-.76l-1.7-5h.9l1.21 3.65 1.25-3.65h.66l1.25 3.65 1.21-3.65h.91l-1.7 5h-.76l-1.24-3.59Z" fill="#F66A0A"/><path opacity=".3" d="M33.96 22.838c-10.764 7.42-19.417 8-19.417 8h110.25s-8.653-.58-19.416-8c-10.764-7.42-23.364-22-35.709-22-12.345 0-24.945 14.58-35.709 22Z" fill="#037DD6"/><mask id="a" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="14" y="0" width="111" height="31"><path d="M33.96 22.705c-10.764 7.42-19.417 8-19.417 8h110.25s-8.653-.58-19.416-8c-10.764-7.42-23.364-22-35.709-22-12.345 0-24.945 14.58-35.709 22Z" fill="#EAF6FF"/></mask><g mask="url(#a)"><path fill="#F66A0A" stroke="#fff" stroke-width="2" d="M12.793 16.838h20.706v17.303H12.793z"/></g></svg>
<svg width="162" height="32" viewBox="0 0 162 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2140_18019)">
<rect width="161" height="31" transform="translate(0.8125 0.0820312)" fill="white"/>
<path d="M26.0469 8.63806H26.9769V14.8581H30.4069V15.7381H26.0469V8.63806Z" fill="#F66A0A"/>
<path d="M33.4591 15.8381C33.0791 15.8381 32.7291 15.7714 32.4091 15.6381C32.0891 15.4981 31.8125 15.3114 31.5791 15.0781C31.3458 14.8447 31.1625 14.5714 31.0291 14.2581C30.9025 13.9381 30.8391 13.5981 30.8391 13.2381C30.8391 12.8781 30.9025 12.5414 31.0291 12.2281C31.1625 11.9081 31.3458 11.6314 31.5791 11.3981C31.8125 11.1647 32.0891 10.9814 32.4091 10.8481C32.7291 10.7081 33.0791 10.6381 33.4591 10.6381C33.8325 10.6381 34.1791 10.7081 34.4991 10.8481C34.8191 10.9814 35.0958 11.1647 35.3291 11.3981C35.5625 11.6314 35.7425 11.9081 35.8691 12.2281C36.0025 12.5414 36.0691 12.8781 36.0691 13.2381C36.0691 13.5981 36.0025 13.9381 35.8691 14.2581C35.7425 14.5714 35.5625 14.8447 35.3291 15.0781C35.0958 15.3114 34.8191 15.4981 34.4991 15.6381C34.1791 15.7714 33.8325 15.8381 33.4591 15.8381ZM33.4591 15.0381C33.7191 15.0381 33.9558 14.9914 34.1691 14.8981C34.3825 14.8047 34.5625 14.6781 34.7091 14.5181C34.8625 14.3514 34.9791 14.1614 35.0591 13.9481C35.1458 13.7281 35.1891 13.4914 35.1891 13.2381C35.1891 12.9914 35.1458 12.7581 35.0591 12.5381C34.9791 12.3181 34.8625 12.1281 34.7091 11.9681C34.5625 11.8014 34.3825 11.6714 34.1691 11.5781C33.9558 11.4847 33.7191 11.4381 33.4591 11.4381C33.1991 11.4381 32.9625 11.4847 32.7491 11.5781C32.5358 11.6714 32.3525 11.8014 32.1991 11.9681C32.0458 12.1281 31.9258 12.3181 31.8391 12.5381C31.7591 12.7581 31.7191 12.9914 31.7191 13.2381C31.7191 13.4914 31.7591 13.7281 31.8391 13.9481C31.9258 14.1614 32.0458 14.3514 32.1991 14.5181C32.3525 14.6781 32.5358 14.8047 32.7491 14.8981C32.9625 14.9914 33.1991 15.0381 33.4591 15.0381Z" fill="#F66A0A"/>
<path d="M40.2841 12.1481L39.0541 15.7381H38.2941L36.5941 10.7381H37.4941L38.7041 14.3881L39.9541 10.7381H40.6141L41.8641 14.3881L43.0741 10.7381H43.9841L42.2841 15.7381H41.5241L40.2841 12.1481Z" fill="#F66A0A"/>
<path opacity="0.3" d="M45.774 22.738C35.0101 30.158 26.3574 30.738 26.3574 30.738H136.608C136.608 30.738 127.955 30.158 117.191 22.738C106.427 15.3181 93.8273 0.738037 81.4825 0.738037C69.1377 0.738037 56.5378 15.3181 45.774 22.738Z" fill="#037DD6"/>
<mask id="mask0_2140_18019" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="26" y="0" width="111" height="31">
<path d="M45.774 22.6052C35.0101 30.0252 26.3574 30.6052 26.3574 30.6052H136.608C136.608 30.6052 127.955 30.0252 117.191 22.6052C106.427 15.1853 93.8273 0.605225 81.4825 0.605225C69.1377 0.605225 56.5378 15.1853 45.774 22.6052Z" fill="#EAF6FF"/>
</mask>
<g mask="url(#mask0_2140_18019)">
<rect x="24.6074" y="16.738" width="20.7061" height="17.3027" fill="#F66A0A" stroke="white" stroke-width="2"/>
</g>
</g>
<defs>
<clipPath id="clip0_2140_18019">
<rect width="161" height="31" fill="white" transform="translate(0.8125 0.0820312)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

@ -1 +1,16 @@
<svg width="111" height="49" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="m38 4.334 2.63 3.54 2.65-3.54h.84v7.1h-.92v-5.52l-2.56 3.44-2.56-3.44v5.52h-.93v-7.1H38Zm9.99 7.2a2.73 2.73 0 0 1-1.06-.2 2.431 2.431 0 0 1-1.35-1.37 2.91 2.91 0 0 1-.18-1.03c0-.367.063-.707.19-1.02.126-.32.303-.597.53-.83.233-.234.51-.417.83-.55.32-.134.67-.2 1.05-.2.326 0 .633.056.92.17.286.106.536.27.75.49.22.213.393.476.52.79.126.306.19.66.19 1.06v.13c0 .033-.004.076-.01.13h-4.1c.006.233.053.45.14.65.093.2.213.373.36.52.153.146.333.263.54.35.213.08.443.12.69.12.386 0 .703-.077.95-.23.253-.16.456-.374.61-.64l.68.47c-.227.366-.53.656-.91.87-.374.213-.82.32-1.34.32Zm1.51-3.13a1.579 1.579 0 0 0-.19-.55c-.087-.16-.2-.297-.34-.41a1.419 1.419 0 0 0-.46-.26 1.639 1.639 0 0 0-.54-.09 1.729 1.729 0 0 0-1.04.35 1.5 1.5 0 0 0-.38.41c-.107.16-.18.343-.22.55h3.17Zm4.16 3.13c-.353 0-.68-.067-.98-.2a2.57 2.57 0 0 1-.77-.56 2.73 2.73 0 0 1-.52-.83 2.8 2.8 0 0 1-.18-1.01c0-.36.06-.697.18-1.01.127-.314.3-.587.52-.82a2.359 2.359 0 0 1 1.75-.77c.38 0 .727.086 1.04.26.314.166.554.37.72.61v-3.27h.86v7.5h-.86v-.77a2.08 2.08 0 0 1-.72.62 2.18 2.18 0 0 1-1.04.25Zm.13-.79a1.607 1.607 0 0 0 1.22-.52c.154-.167.27-.36.35-.58.087-.22.13-.457.13-.71 0-.254-.043-.49-.13-.71-.08-.22-.196-.41-.35-.57a1.544 1.544 0 0 0-.53-.39 1.658 1.658 0 0 0-.69-.14c-.253 0-.486.046-.7.14a1.648 1.648 0 0 0-.54.39 1.82 1.82 0 0 0-.35.57c-.08.22-.12.456-.12.71 0 .253.04.49.12.71.087.22.204.413.35.58.154.16.334.286.54.38.214.093.447.14.7.14Zm4.55-5.65a.599.599 0 0 1-.62-.61c0-.167.06-.31.18-.43s.266-.18.44-.18c.172 0 .316.06.43.18.112.12.17.263.17.43 0 .173-.058.32-.17.44a.583.583 0 0 1-.43.17Zm-.44 1.34h.86v5h-.86v-5Zm4.26 5.1c-.274 0-.524-.047-.75-.14-.227-.1-.42-.237-.58-.41a1.917 1.917 0 0 1-.38-.63 2.617 2.617 0 0 1-.13-.85v-3.07h.86v2.94c0 .42.1.753.3 1 .206.246.483.37.83.37.193 0 .373-.04.54-.12.173-.087.32-.207.44-.36.126-.16.226-.354.3-.58.073-.227.11-.484.11-.77v-2.48h.86v5h-.86v-.77a1.654 1.654 0 0 1-.66.66c-.267.14-.56.21-.88.21Zm10.46-3.04c0-.407-.083-.737-.25-.99-.166-.254-.413-.38-.74-.38-.4 0-.726.156-.98.47-.246.313-.376.74-.39 1.28v2.56h-.86v-2.94c0-.407-.083-.737-.25-.99-.16-.254-.403-.38-.73-.38-.406 0-.74.163-1 .49-.253.326-.38.773-.38 1.34v2.48h-.86v-5h.86v.77a1.63 1.63 0 0 1 .61-.63c.26-.16.56-.24.9-.24.374 0 .69.096.95.29.267.186.464.446.59.78.134-.327.344-.587.63-.78.294-.194.63-.29 1.01-.29.274 0 .517.05.73.15.22.093.404.23.55.41.154.173.27.386.35.64.08.246.12.523.12.83v3.07h-.86v-2.94Z" fill="#037DD6"/><path opacity=".3" d="M19.506 40.566c-10.763 7.42-19.416 8-19.416 8h110.25s-8.653-.58-19.417-8c-10.763-7.42-23.363-22-35.708-22-12.345 0-24.945 14.58-35.709 22Z" fill="#037DD6"/><mask id="a" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="18" width="111" height="31"><path d="M19.506 40.434c-10.763 7.42-19.416 8-19.416 8h110.25s-8.653-.58-19.417-8c-10.763-7.42-23.363-22-35.708-22-12.345 0-24.945 14.58-35.709 22Z" fill="#EAF6FF"/></mask><g mask="url(#a)"><path fill="#037DD6" stroke="#fff" stroke-width="2" d="M36.047 12.619H73.39v39.25H36.047z"/></g></svg>
<svg width="162" height="47" viewBox="0 0 162 47" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="161" height="46" transform="translate(0.8125 0.0820312)" fill="white"/>
<path d="M64.268 1.50517L66.898 5.04517L69.548 1.50517H70.388V8.60517H69.468V3.08517L66.908 6.52517L64.348 3.08517V8.60517H63.418V1.50517H64.268Z" fill="#037DD6"/>
<path d="M74.2575 8.70517C73.8708 8.70517 73.5175 8.6385 73.1975 8.50517C72.8775 8.37183 72.6042 8.1885 72.3775 7.95517C72.1508 7.72183 71.9742 7.4485 71.8475 7.13517C71.7275 6.81517 71.6675 6.47183 71.6675 6.10517C71.6675 5.7385 71.7308 5.3985 71.8575 5.08517C71.9842 4.76517 72.1608 4.4885 72.3875 4.25517C72.6208 4.02183 72.8975 3.8385 73.2175 3.70517C73.5375 3.57183 73.8875 3.50517 74.2675 3.50517C74.5942 3.50517 74.9008 3.56183 75.1875 3.67517C75.4742 3.78183 75.7242 3.94517 75.9375 4.16517C76.1575 4.3785 76.3308 4.64183 76.4575 4.95517C76.5842 5.26183 76.6475 5.61517 76.6475 6.01517C76.6475 6.06183 76.6475 6.10517 76.6475 6.14517C76.6475 6.1785 76.6442 6.22183 76.6375 6.27517H72.5375C72.5442 6.5085 72.5908 6.72517 72.6775 6.92517C72.7708 7.12517 72.8908 7.2985 73.0375 7.44517C73.1908 7.59183 73.3708 7.7085 73.5775 7.79517C73.7908 7.87517 74.0208 7.91517 74.2675 7.91517C74.6542 7.91517 74.9708 7.8385 75.2175 7.68517C75.4708 7.52517 75.6742 7.31183 75.8275 7.04517L76.5075 7.51517C76.2808 7.88183 75.9775 8.17183 75.5975 8.38517C75.2242 8.5985 74.7775 8.70517 74.2575 8.70517ZM75.7675 5.57517C75.7342 5.3685 75.6708 5.18517 75.5775 5.02517C75.4908 4.86517 75.3775 4.7285 75.2375 4.61517C75.1042 4.50183 74.9508 4.41517 74.7775 4.35517C74.6042 4.29517 74.4242 4.26517 74.2375 4.26517C74.0508 4.26517 73.8675 4.29517 73.6875 4.35517C73.5075 4.41517 73.3442 4.50183 73.1975 4.61517C73.0508 4.72183 72.9242 4.8585 72.8175 5.02517C72.7108 5.18517 72.6375 5.3685 72.5975 5.57517H75.7675Z" fill="#037DD6"/>
<path d="M79.928 8.70517C79.5747 8.70517 79.248 8.6385 78.948 8.50517C78.6547 8.36517 78.398 8.1785 78.178 7.94517C77.958 7.70517 77.7847 7.4285 77.658 7.11517C77.538 6.80183 77.478 6.46517 77.478 6.10517C77.478 5.74517 77.538 5.4085 77.658 5.09517C77.7847 4.78183 77.958 4.5085 78.178 4.27517C78.398 4.03517 78.6547 3.8485 78.948 3.71517C79.248 3.57517 79.5747 3.50517 79.928 3.50517C80.308 3.50517 80.6547 3.59183 80.968 3.76517C81.2814 3.93183 81.5214 4.13517 81.688 4.37517V1.10517H82.548V8.60517H81.688V7.83517C81.5214 8.07517 81.2814 8.28183 80.968 8.45517C80.6547 8.62183 80.308 8.70517 79.928 8.70517ZM80.058 7.91517C80.3114 7.91517 80.5414 7.8685 80.748 7.77517C80.9547 7.68183 81.1314 7.55517 81.278 7.39517C81.4314 7.2285 81.548 7.03517 81.628 6.81517C81.7147 6.59517 81.758 6.3585 81.758 6.10517C81.758 5.85183 81.7147 5.61517 81.628 5.39517C81.548 5.17517 81.4314 4.98517 81.278 4.82517C81.1314 4.6585 80.9547 4.5285 80.748 4.43517C80.5414 4.34183 80.3114 4.29517 80.058 4.29517C79.8047 4.29517 79.5714 4.34183 79.358 4.43517C79.1514 4.5285 78.9714 4.6585 78.818 4.82517C78.6714 4.98517 78.5547 5.17517 78.468 5.39517C78.388 5.61517 78.348 5.85183 78.348 6.10517C78.348 6.3585 78.388 6.59517 78.468 6.81517C78.5547 7.03517 78.6714 7.2285 78.818 7.39517C78.9714 7.55517 79.1514 7.68183 79.358 7.77517C79.5714 7.8685 79.8047 7.91517 80.058 7.91517Z" fill="#037DD6"/>
<path d="M84.6069 2.26517C84.4335 2.26517 84.2869 2.2085 84.1669 2.09517C84.0469 1.97517 83.9869 1.8285 83.9869 1.65517C83.9869 1.4885 84.0469 1.34517 84.1669 1.22517C84.2869 1.10517 84.4335 1.04517 84.6069 1.04517C84.7802 1.04517 84.9235 1.10517 85.0369 1.22517C85.1502 1.34517 85.2069 1.4885 85.2069 1.65517C85.2069 1.8285 85.1502 1.97517 85.0369 2.09517C84.9235 2.2085 84.7802 2.26517 84.6069 2.26517ZM84.1669 3.60517H85.0269V8.60517H84.1669V3.60517Z" fill="#037DD6"/>
<path d="M88.4271 8.70517C88.1538 8.70517 87.9038 8.6585 87.6771 8.56517C87.4504 8.46517 87.2571 8.3285 87.0971 8.15517C86.9371 7.98183 86.8104 7.77183 86.7171 7.52517C86.6304 7.27183 86.5871 6.9885 86.5871 6.67517V3.60517H87.4471V6.54517C87.4471 6.96517 87.5471 7.2985 87.7471 7.54517C87.9538 7.79183 88.2304 7.91517 88.5771 7.91517C88.7704 7.91517 88.9504 7.87517 89.1171 7.79517C89.2904 7.7085 89.4371 7.5885 89.5571 7.43517C89.6838 7.27517 89.7838 7.08183 89.8571 6.85517C89.9304 6.6285 89.9671 6.37183 89.9671 6.08517V3.60517H90.8271V8.60517H89.9671V7.83517C89.8004 8.1285 89.5804 8.3485 89.3071 8.49517C89.0404 8.63517 88.7471 8.70517 88.4271 8.70517Z" fill="#037DD6"/>
<path d="M98.8881 5.66517C98.8881 5.2585 98.8048 4.9285 98.6381 4.67517C98.4715 4.42183 98.2248 4.29517 97.8981 4.29517C97.4981 4.29517 97.1715 4.45183 96.9181 4.76517C96.6715 5.0785 96.5415 5.50517 96.5281 6.04517V8.60517H95.6681V5.66517C95.6681 5.2585 95.5848 4.9285 95.4181 4.67517C95.2581 4.42183 95.0148 4.29517 94.6881 4.29517C94.2815 4.29517 93.9481 4.4585 93.6881 4.78517C93.4348 5.11183 93.3081 5.5585 93.3081 6.12517V8.60517H92.4481V3.60517H93.3081V4.37517C93.4548 4.1085 93.6581 3.8985 93.9181 3.74517C94.1781 3.58517 94.4781 3.50517 94.8181 3.50517C95.1915 3.50517 95.5081 3.60183 95.7681 3.79517C96.0348 3.98183 96.2315 4.24183 96.3581 4.57517C96.4915 4.2485 96.7015 3.9885 96.9881 3.79517C97.2815 3.60183 97.6181 3.50517 97.9981 3.50517C98.2715 3.50517 98.5148 3.55517 98.7281 3.65517C98.9481 3.7485 99.1315 3.88517 99.2781 4.06517C99.4315 4.2385 99.5481 4.45183 99.6281 4.70517C99.7081 4.95183 99.7481 5.2285 99.7481 5.53517V8.60517H98.8881V5.66517Z" fill="#037DD6"/>
<path opacity="0.3" d="M45.774 37.738C35.0101 45.158 26.3574 45.738 26.3574 45.738H136.608C136.608 45.738 127.955 45.158 117.191 37.738C106.427 30.3181 93.8273 15.738 81.4825 15.738C69.1377 15.738 56.5378 30.3181 45.774 37.738Z" fill="#037DD6"/>
<mask id="mask0_2140_17999" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="26" y="15" width="111" height="31">
<path d="M45.774 37.6052C35.0101 45.0252 26.3574 45.6052 26.3574 45.6052H136.608C136.608 45.6052 127.955 45.0252 117.191 37.6052C106.427 30.1853 93.8273 15.6052 81.4825 15.6052C69.1377 15.6052 56.5378 30.1853 45.774 37.6052Z" fill="#EAF6FF"/>
</mask>
<g mask="url(#mask0_2140_17999)">
<rect x="62.3125" y="9.79077" width="37.3435" height="39.25" fill="#037DD6" stroke="white" stroke-width="2"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

@ -0,0 +1,116 @@
WEBVTT
1
00:00:00.780 --> 00:00:04.580
MetaMask ist eine neue Möglichkeit, sich mit
Webseiten und Anwendungen zu verbinden.
2
00:00:04.580 --> 00:00:08.860
Auf traditionellen Webseiten ist eine
zentrale Datenbank für die Steuerung und
3
00:00:08.860 --> 00:00:10.179
Wiederherstellung der Konten zuständig.
4
00:00:10.179 --> 00:00:15.050
Bei MetaMask gehört all diese Macht dem
Besitzer eines „Hauptschlüssels“.
5
00:00:15.050 --> 00:00:18.460
Wer den Schlüssel besitzt, kontrolliert
das Wallet und damit die Konten.
6
00:00:18.460 --> 00:00:21.110
Der geheime Satz zur Wiederherstellung
Deines Wallets ist der „Hauptschlüssel“.
7
00:00:21.110 --> 00:00:26.070
Es ist eine Reihe von 12 Wörtern, welche generiert werden,
wenn MetaMask zum ersten Mal eingerichtet wird, diese erlauben dir
8
00:00:26.070 --> 00:00:30.120
Deinen „Hauptschlüssel“ zu Deinem Wallet wieder herzustellen,
solltest Du jemals den Zugriff darauf verlieren.
9
00:00:30.120 --> 00:00:33.451
Es ist wichtig, dass Du Dein Wallet sicherst,
indem Du Deinen geheimen
10
00:00:33.451 --> 00:00:37.510
Wiederherstellungssatz sicher
und geheim verwahrst.
11
00:00:37.510 --> 00:00:41.429
Wenn jemand Zugriff darauf erhält, hat er
den „Hauptschlüssel“ für Dein Wallet und kann
12
00:00:41.429 --> 00:00:45.190
frei auf Deine Konten zugreifen und alle Gelder stehlen.
13
00:00:45.190 --> 00:00:50.109
Um Dein MetaMask-Wallet zu sichern, musst Du Deinen
geheimen Wiederherstellungssatz sicher speichern.
14
00:00:50.109 --> 00:00:54.930
Du kannst ihn aufschreiben, irgendwo verstecken,
in ein Schließfach legen
15
00:00:54.930 --> 00:00:57.729
oder in einen sicheren Passwort-Manager speichern.
16
00:00:57.729 --> 00:01:01.050
Einige Benutzer gravieren ihren
Satz sogar auf eine Metallplatte.
17
00:01:01.050 --> 00:01:04.440
Niemand, nicht einmal das Team von MetaMask,
kann Dir dabei helfen
18
00:01:04.440 --> 00:01:07.820
Dein Wallet wiederherzustellen, wenn Du Deinen
geheimen Wiederherstellungssatz verlierst.
19
00:01:07.820 --> 00:01:12.072
Wenn Du Deinen geheimen Wiederherstellungssatz noch nicht
aufgeschrieben und an einem sicheren Ort aufbewahrt hast,
20
00:01:12.072 --> 00:01:15.492
mach es jetzt. Wir warten solange.
21
00:01:15.500 --> 00:01:20.780
Und denk daran, teilen Deinen geheimen Wiederherstellungssatz
niemals: nicht einmal mit uns.
22
00:01:20.780 --> 00:01:24.910
Wenn dich jemals jemand danach fragt,
versucht er dich zu betrügen.
23
00:01:24.910 --> 00:01:26.250
Das ist alles!
24
00:01:26.250 --> 00:01:31.020
Jetzt weist Du, was ein geheimer Wiederherstellungssatz ist
und wie Du dafür sorgst, das Dein Wallete sicher bleibt.

@ -1,7 +1,7 @@
<!doctype html>
<html lang="en">
<head>
<title>Ethereum Phishing Detection - MetaMask</title>
<title>MetaMask Phishing Detection</title>
<script src="./globalthis.js" type="text/javascript" charset="utf-8"></script>
<script src="./lockdown-install.js" type="text/javascript" charset="utf-8"></script>
<script src="./lockdown-run.js" type="text/javascript" charset="utf-8"></script>
@ -37,7 +37,7 @@
<img src="./images/info-logo.png" alt="">
<h1>
<i class="fa fa-exclamation-circle" aria-hidden="true"></i>
Ethereum Phishing Detection
MetaMask Phishing Detection
</h1>
</div>
<div class="content__body">

@ -31,10 +31,12 @@ export default class AppStateController extends EventEmitter {
recoveryPhraseReminderHasBeenShown: false,
recoveryPhraseReminderLastShown: new Date().getTime(),
collectiblesDetectionNoticeDismissed: false,
enableEIP1559V2NoticeDismissed: false,
showTestnetMessageInDropdown: true,
trezorModel: null,
...initState,
qrHardware: {},
collectiblesDropdownState: {},
});
this.timer = null;
@ -270,4 +272,26 @@ export default class AppStateController extends EventEmitter {
collectiblesDetectionNoticeDismissed,
});
}
/**
* A setter for the `enableEIP1559V2NoticeDismissed` property
*
* @param enableEIP1559V2NoticeDismissed
*/
setEnableEIP1559V2NoticeDismissed(enableEIP1559V2NoticeDismissed) {
this.store.updateState({
enableEIP1559V2NoticeDismissed,
});
}
/**
* A setter for the `collectiblesDropdownState` property
*
* @param collectiblesDropdownState
*/
updateCollectibleDropDownState(collectiblesDropdownState) {
this.store.updateState({
collectiblesDropdownState,
});
}
}

@ -1,11 +1,13 @@
import { merge, omit } from 'lodash';
import { merge, omit, omitBy } from 'lodash';
import { ObservableStore } from '@metamask/obs-store';
import { bufferToHex, keccak } from 'ethereumjs-util';
import { generateUUID } from 'pubnub';
import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../shared/constants/app';
import {
METAMETRICS_ANONYMOUS_ID,
METAMETRICS_BACKGROUND_PAGE_OBJECT,
} from '../../../shared/constants/metametrics';
import { SECOND } from '../../../shared/constants/time';
const defaultCaptureException = (err) => {
// throw error on clean stack so its captured by platform integrations (eg sentry)
@ -27,15 +29,18 @@ const exceptionsToFilter = {
* @typedef {import('../../../shared/constants/metametrics').SegmentInterface} SegmentInterface
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsPagePayload} MetaMetricsPagePayload
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsPageOptions} MetaMetricsPageOptions
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsEventFragment} MetaMetricsEventFragment
*/
/**
* @typedef {Object} MetaMetricsControllerState
* @property {?string} metaMetricsId - The user's metaMetricsId that will be
* @property {string} [metaMetricsId] - The user's metaMetricsId that will be
* attached to all non-anonymized event payloads
* @property {?boolean} participateInMetaMetrics - The user's preference for
* @property {boolean} [participateInMetaMetrics] - The user's preference for
* participating in the MetaMetrics analytics program. This setting controls
* whether or not events are tracked
* @property {{[string]: MetaMetricsEventFragment}} [fragments] - Object keyed
* by UUID with stored fragments as values.
*/
export default class MetaMetricsController {
@ -81,10 +86,15 @@ export default class MetaMetricsController {
this.version =
environment === 'production' ? version : `${version}-${environment}`;
const abandonedFragments = omitBy(initState?.fragments, 'persist');
this.store = new ObservableStore({
participateInMetaMetrics: null,
metaMetricsId: null,
...initState,
fragments: {
...initState?.fragments,
},
});
preferencesStore.subscribe(({ currentLocale }) => {
@ -96,6 +106,32 @@ export default class MetaMetricsController {
this.network = getNetworkIdentifier();
});
this.segment = segment;
// Track abandoned fragments that weren't properly cleaned up.
// Abandoned fragments are those that were stored in persistent memory
// and are available at controller instance creation, but do not have the
// 'persist' flag set. This means anytime the extension is unlocked, any
// fragments that are not marked as persistent will be purged and the
// failure event will be emitted.
Object.values(abandonedFragments).forEach((fragment) => {
this.finalizeEventFragment(fragment.id, { abandoned: true });
});
// 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
// a timeout can be specified that will cause an abandoned event to be
// tracked if the event isn't progressed within that amount of time.
setInterval(() => {
Object.values(this.store.getState().fragments).forEach((fragment) => {
if (
fragment.timeout &&
Date.now() - fragment.lastUpdated / 1000 > fragment.timeout
) {
this.finalizeEventFragment(fragment.id, { abandoned: true });
}
});
}, SECOND * 30);
}
generateMetaMetricsId() {
@ -109,6 +145,141 @@ export default class MetaMetricsController {
);
}
/**
* Create an event fragment in state and returns the event fragment object.
*
* @param {MetaMetricsEventFragment} options - Fragment settings and properties
* to initiate the fragment with.
* @returns {MetaMetricsEventFragment}
*/
createEventFragment(options) {
if (!options.successEvent || !options.category) {
throw new Error(
`Must specify success event and category. Success event was: ${
options.event
}. Category was: ${options.category}. Payload keys were: ${Object.keys(
options,
)}. ${
typeof options.properties === 'object'
? `Payload property keys were: ${Object.keys(options.properties)}`
: ''
}`,
);
}
const { fragments } = this.store.getState();
const id = options.uniqueIdentifier ?? generateUUID();
const fragment = {
id,
...options,
lastUpdated: Date.now(),
};
this.store.updateState({
fragments: {
...fragments,
[id]: fragment,
},
});
if (options.initialEvent) {
this.trackEvent({
event: fragment.initialEvent,
category: fragment.category,
properties: fragment.properties,
sensitiveProperties: fragment.sensitiveProperties,
page: fragment.page,
referrer: fragment.referrer,
revenue: fragment.revenue,
value: fragment.value,
currency: fragment.currency,
environmentType: fragment.environmentType,
});
}
return fragment;
}
/**
* Returns the fragment stored in memory with provided id or undefined if it
* does not exist.
*
* @param {string} id - id of fragment to retrieve
* @returns {[MetaMetricsEventFragment]}
*/
getEventFragmentById(id) {
const { fragments } = this.store.getState();
const fragment = fragments[id];
return fragment;
}
/**
* Updates an event fragment in state
*
* @param {string} id - The fragment id to update
* @param {MetaMetricsEventFragment} payload - Fragment settings and
* properties to initiate the fragment with.
*/
updateEventFragment(id, payload) {
const { fragments } = this.store.getState();
const fragment = fragments[id];
if (!fragment) {
throw new Error(`Event fragment with id ${id} does not exist.`);
}
this.store.updateState({
fragments: {
...fragments,
[id]: merge(fragments[id], {
...payload,
lastUpdated: Date.now(),
}),
},
});
}
/**
* Finalizes a fragment, tracking either a success event or failure Event
* and then removes the fragment from state.
*
* @param {string} id - UUID of the event fragment to be closed
* @param {object} options
* @param {boolean} [options.abandoned] - if true track the failure
* event instead of the success event
* @param {MetaMetricsContext.page} [options.page] - page the final event
* occurred on. This will override whatever is set on the fragment
* @param {MetaMetricsContext.referrer} [options.referrer] - Dapp that
* originated the fragment. This is for fallback only, the fragment referrer
* property will take precedence.
*/
finalizeEventFragment(id, { abandoned = false, page, referrer } = {}) {
const fragment = this.store.getState().fragments[id];
if (!fragment) {
throw new Error(`Funnel with id ${id} does not exist.`);
}
const eventName = abandoned ? fragment.failureEvent : fragment.successEvent;
this.trackEvent({
event: eventName,
category: fragment.category,
properties: fragment.properties,
sensitiveProperties: fragment.sensitiveProperties,
page: page ?? fragment.page,
referrer: fragment.referrer ?? referrer,
revenue: fragment.revenue,
value: fragment.value,
currency: fragment.currency,
environmentType: fragment.environmentType,
});
const { fragments } = this.store.getState();
delete fragments[id];
this.store.updateState({ fragments });
}
/**
* Setter for the `participateInMetaMetrics` property
*

@ -81,6 +81,28 @@ function getMockPreferencesStore({ currentLocale = LOCALE } = {}) {
};
}
const SAMPLE_PERSISTED_EVENT = {
id: 'testid',
persist: true,
category: 'Unit Test',
successEvent: 'sample persisted event success',
failureEvent: 'sample persisted event failure',
properties: {
test: true,
},
};
const SAMPLE_NON_PERSISTED_EVENT = {
id: 'testid2',
persist: false,
category: 'Unit Test',
successEvent: 'sample non-persisted event success',
failureEvent: 'sample non-persisted event failure',
properties: {
test: true,
},
};
function getMetaMetricsController({
participateInMetaMetrics = true,
metaMetricsId = TEST_META_METRICS_ID,
@ -105,12 +127,29 @@ function getMetaMetricsController({
initState: {
participateInMetaMetrics,
metaMetricsId,
fragments: {
testid: SAMPLE_PERSISTED_EVENT,
testid2: SAMPLE_NON_PERSISTED_EVENT,
},
},
});
}
describe('MetaMetricsController', function () {
describe('constructor', function () {
it('should properly initialize', function () {
const mock = sinon.mock(segment);
mock
.expects('track')
.once()
.withArgs({
event: 'sample non-persisted event failure',
userId: TEST_META_METRICS_ID,
context: DEFAULT_TEST_CONTEXT,
properties: {
...DEFAULT_EVENT_PROPERTIES,
test: true,
},
});
const metaMetricsController = getMetaMetricsController();
assert.strictEqual(metaMetricsController.version, VERSION);
assert.strictEqual(metaMetricsController.network, NETWORK);
@ -127,6 +166,10 @@ describe('MetaMetricsController', function () {
metaMetricsController.locale,
LOCALE.replace('_', '-'),
);
assert.deepStrictEqual(metaMetricsController.state.fragments, {
testid: SAMPLE_PERSISTED_EVENT,
});
mock.verify();
});
it('should update when network changes', function () {

@ -167,6 +167,15 @@ export default class PreferencesController {
this.store.updateState({ advancedGasFee: val });
}
/**
* Setter for the `eip1559V2Enabled` property
*
* @param {object} val - holds the eip1559V2Enabled that the user set as experimental settings.
*/
setEIP1559V2Enabled(val) {
this.store.updateState({ eip1559V2Enabled: val });
}
/**
* Add new methodData to state, to avoid requesting this information again through Infura
*

@ -25,6 +25,7 @@ import {
TRANSACTION_STATUSES,
TRANSACTION_TYPES,
TRANSACTION_ENVELOPE_TYPES,
TRANSACTION_EVENTS,
} from '../../../../shared/constants/transaction';
import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../ui/helpers/constants/transactions';
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
@ -54,13 +55,10 @@ const hstInterface = new ethers.utils.Interface(abi);
const MAX_MEMSTORE_TX_LIST_SIZE = 100; // Number of transactions (by unique nonces) to keep in memory
export const TRANSACTION_EVENTS = {
ADDED: 'Transaction Added',
APPROVED: 'Transaction Approved',
FINALIZED: 'Transaction Finalized',
REJECTED: 'Transaction Rejected',
SUBMITTED: 'Transaction Submitted',
};
/**
* @typedef {import('../../../../shared/constants/transaction').TransactionMeta} TransactionMeta
* @typedef {import('../../../../shared/constants/transaction').TransactionMetaMetricsEventString} TransactionMetaMetricsEventString
*/
/**
* @typedef {Object} CustomGasSettings
@ -118,6 +116,10 @@ export default class TransactionController extends EventEmitter {
this._trackMetaMetricsEvent = opts.trackMetaMetricsEvent;
this._getParticipateInMetrics = opts.getParticipateInMetrics;
this._getEIP1559GasFeeEstimates = opts.getEIP1559GasFeeEstimates;
this.createEventFragment = opts.createEventFragment;
this.updateEventFragment = opts.updateEventFragment;
this.finalizeEventFragment = opts.finalizeEventFragment;
this.getEventFragmentById = opts.getEventFragmentById;
this.memStore = new ObservableStore({});
this.query = new EthQuery(this.provider);
@ -441,7 +443,8 @@ export default class TransactionController extends EventEmitter {
}
if (eip1559Compatibility) {
if (process.env.EIP_1559_V2 && Boolean(advancedGasFeeDefaultValues)) {
const { eip1559V2Enabled } = this.preferencesStore.getState();
if (eip1559V2Enabled && Boolean(advancedGasFeeDefaultValues)) {
txMeta.userFeeLevel = CUSTOM_GAS_ESTIMATE;
txMeta.txParams.maxFeePerGas = decGWEIToHexWEI(
advancedGasFeeDefaultValues.maxBaseFee,
@ -458,7 +461,7 @@ export default class TransactionController extends EventEmitter {
// then we set maxFeePerGas and maxPriorityFeePerGas to the suggested gasPrice.
txMeta.txParams.maxFeePerGas = txMeta.txParams.gasPrice;
txMeta.txParams.maxPriorityFeePerGas = txMeta.txParams.gasPrice;
if (process.env.EIP_1559_V2) {
if (eip1559V2Enabled) {
txMeta.userFeeLevel = PRIORITY_LEVELS.DAPP_SUGGESTED;
} else {
txMeta.userFeeLevel = CUSTOM_GAS_ESTIMATE;
@ -472,7 +475,7 @@ export default class TransactionController extends EventEmitter {
txMeta.origin === 'metamask'
) {
txMeta.userFeeLevel = GAS_RECOMMENDATIONS.MEDIUM;
} else if (process.env.EIP_1559_V2) {
} else if (eip1559V2Enabled) {
txMeta.userFeeLevel = PRIORITY_LEVELS.DAPP_SUGGESTED;
} else {
txMeta.userFeeLevel = CUSTOM_GAS_ESTIMATE;
@ -538,6 +541,7 @@ export default class TransactionController extends EventEmitter {
if (defaultGasLimit && !txMeta.txParams.gas) {
txMeta.txParams.gas = defaultGasLimit;
txMeta.originalGasEstimate = defaultGasLimit;
}
return txMeta;
}
@ -661,9 +665,8 @@ export default class TransactionController extends EventEmitter {
* which is defined by specifying a numerator. 11 is a 10% bump, 12 would be
* a 20% bump, and so on.
*
* @param {import(
* '../../../../shared/constants/transaction'
* ).TransactionMeta} originalTxMeta - Original transaction to use as base
* @param {TransactionMeta} originalTxMeta - Original transaction to use as
* base
* @param {CustomGasSettings} [customGasSettings] - overrides for the gas
* fields to use instead of the multiplier
* @param {number} [incrementNumerator] - Numerator from which to generate a
@ -1119,6 +1122,28 @@ export default class TransactionController extends EventEmitter {
this.txStateManager.updateTransaction(txMeta, 'transactions#setTxHash');
}
/**
* Convenience method for the UI to easily create event fragments when the
* fragment does not exist in state.
*
* @param {number} transactionId - The transaction id to create the event
* fragment for
* @param {valueOf<TRANSACTION_EVENTS>} event - event type to create
*/
createTransactionEventFragment(transactionId, event) {
const txMeta = this.txStateManager.getTransaction(transactionId);
const {
properties,
sensitiveProperties,
} = this._buildEventFragmentProperties(txMeta);
this._createTransactionEventFragment(
txMeta,
event,
properties,
sensitiveProperties,
);
}
//
// PRIVATE METHODS
//
@ -1449,20 +1474,7 @@ export default class TransactionController extends EventEmitter {
}
}
/**
* Extracts relevant properties from a transaction meta
* object and uses them to create and send metrics for various transaction
* events.
*
* @param {Object} txMeta - the txMeta object
* @param {string} event - the name of the transaction event
* @param {Object} extraParams - optional props and values to include in sensitiveProperties
*/
_trackTransactionMetricsEvent(txMeta, event, extraParams = {}) {
if (!txMeta) {
return;
}
_buildEventFragmentProperties(txMeta, extraParams) {
const {
type,
time,
@ -1500,27 +1512,202 @@ export default class TransactionController extends EventEmitter {
const gasParamsInGwei = this._getGasValuesInGWEI(gasParams);
this._trackMetaMetricsEvent({
const properties = {
chain_id: chainId,
referrer,
source,
network,
type,
};
const sensitiveProperties = {
status,
transaction_envelope_type: isEIP1559Transaction(txMeta)
? TRANSACTION_ENVELOPE_TYPE_NAMES.FEE_MARKET
: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
first_seen: time,
gas_limit: gasLimit,
...gasParamsInGwei,
...extraParams,
};
return { properties, sensitiveProperties };
}
/**
* Helper method that checks for the presence of an existing fragment by id
* appropriate for the type of event that triggered fragment creation. If the
* appropriate fragment exists, then nothing is done. If it does not exist a
* new event fragment is created with the appropriate payload.
*
* @param {TransactionMeta} txMeta - Transaction meta object
* @param {TransactionMetaMetricsEventString} event - The event type that
* triggered fragment creation
* @param {Object} properties - properties to include in the fragment
* @param {Object} [sensitiveProperties] - sensitive properties to include in
* the fragment
*/
_createTransactionEventFragment(
txMeta,
event,
properties,
sensitiveProperties,
) {
const isSubmitted = [
TRANSACTION_EVENTS.FINALIZED,
TRANSACTION_EVENTS.SUBMITTED,
].includes(event);
const uniqueIdentifier = `transaction-${
isSubmitted ? 'submitted' : 'added'
}-${txMeta.id}`;
const fragment = this.getEventFragmentById(uniqueIdentifier);
if (typeof fragment !== 'undefined') {
return;
}
switch (event) {
// When a transaction is added to the controller, we know that the user
// will be presented with a confirmation screen. The user will then
// either confirm or reject that transaction. Each has an associated
// event we want to track. While we don't necessarily need an event
// fragment to model this, having one allows us to record additional
// properties onto the event from the UI. For example, when the user
// edits the transactions gas params we can record that property and
// then get analytics on the number of transactions in which gas edits
// occur.
case TRANSACTION_EVENTS.ADDED:
this.createEventFragment({
category: 'Transactions',
initialEvent: TRANSACTION_EVENTS.ADDED,
successEvent: TRANSACTION_EVENTS.APPROVED,
failureEvent: TRANSACTION_EVENTS.REJECTED,
properties,
sensitiveProperties,
persist: true,
uniqueIdentifier,
});
break;
// If for some reason an approval or rejection occurs without the added
// fragment existing in memory, we create the added fragment but without
// the initialEvent firing. This is to prevent possible duplication of
// events. A good example why this might occur is if the user had
// unapproved transactions in memory when updating to the version that
// includes this change. A migration would have also helped here but this
// implementation hardens against other possible bugs where a fragment
// does not exist.
case TRANSACTION_EVENTS.APPROVED:
case TRANSACTION_EVENTS.REJECTED:
this.createEventFragment({
category: 'Transactions',
successEvent: TRANSACTION_EVENTS.APPROVED,
failureEvent: TRANSACTION_EVENTS.REJECTED,
properties,
sensitiveProperties,
persist: true,
uniqueIdentifier,
});
break;
// When a transaction is submitted it will always result in updating
// to a finalized state (dropped, failed, confirmed) -- eventually.
// However having a fragment started at this stage allows augmenting
// analytics data with user interactions such as speeding up and
// canceling the transactions. From this controllers perspective a new
// transaction with a new id is generated for speed up and cancel
// transactions, but from the UI we could augment the previous ID with
// supplemental data to show user intent. Such as when they open the
// cancel UI but don't submit. We can record that this happened and add
// properties to the transaction event.
case TRANSACTION_EVENTS.SUBMITTED:
this.createEventFragment({
category: 'Transactions',
initialEvent: TRANSACTION_EVENTS.SUBMITTED,
successEvent: TRANSACTION_EVENTS.FINALIZED,
properties,
sensitiveProperties,
persist: true,
uniqueIdentifier,
});
break;
// If for some reason a transaction is finalized without the submitted
// fragment existing in memory, we create the submitted fragment but
// without the initialEvent firing. This is to prevent possible
// duplication of events. A good example why this might occur is if th
// user had pending transactions in memory when updating to the version
// that includes this change. A migration would have also helped here but
// this implementation hardens against other possible bugs where a
// fragment does not exist.
case TRANSACTION_EVENTS.FINALIZED:
this.createEventFragment({
category: 'Transactions',
successEvent: TRANSACTION_EVENTS.FINALIZED,
properties,
sensitiveProperties,
persist: true,
uniqueIdentifier,
});
break;
default:
break;
}
}
/**
* Extracts relevant properties from a transaction meta
* object and uses them to create and send metrics for various transaction
* events.
*
* @param {Object} txMeta - the txMeta object
* @param {TransactionMetaMetricsEventString} event - the name of the transaction event
* @param {Object} extraParams - optional props and values to include in sensitiveProperties
*/
_trackTransactionMetricsEvent(txMeta, event, extraParams = {}) {
if (!txMeta) {
return;
}
const {
properties,
sensitiveProperties,
} = this._buildEventFragmentProperties(txMeta, extraParams);
// Create event fragments for event types that spawn fragments, and ensure
// existence of fragments for event types that act upon them.
this._createTransactionEventFragment(
txMeta,
event,
category: 'Transactions',
properties: {
chain_id: chainId,
referrer,
source,
network,
type,
},
sensitiveProperties: {
status,
transaction_envelope_type: isEIP1559Transaction(txMeta)
? TRANSACTION_ENVELOPE_TYPE_NAMES.FEE_MARKET
: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
first_seen: time,
gas_limit: gasLimit,
...gasParamsInGwei,
...extraParams,
},
});
properties,
sensitiveProperties,
);
let id;
switch (event) {
// If the user approves a transaction, finalize the transaction added
// event fragment.
case TRANSACTION_EVENTS.APPROVED:
id = `transaction-added-${txMeta.id}`;
this.updateEventFragment(id, { properties, sensitiveProperties });
this.finalizeEventFragment(id);
break;
// If the user rejects a transaction, finalize the transaction added
// event fragment. with the abandoned flag set.
case TRANSACTION_EVENTS.REJECTED:
id = `transaction-added-${txMeta.id}`;
this.updateEventFragment(id, { properties, sensitiveProperties });
this.finalizeEventFragment(id, {
abandoned: true,
});
break;
// When a transaction is finalized, also finalize the transaction
// submitted event fragment.
case TRANSACTION_EVENTS.FINALIZED:
id = `transaction-submitted-${txMeta.id}`;
this.updateEventFragment(id, { properties, sensitiveProperties });
this.finalizeEventFragment(`transaction-submitted-${txMeta.id}`);
break;
default:
break;
}
}
_getTransactionCompletionTime(submittedTime) {

@ -13,6 +13,7 @@ import {
TRANSACTION_STATUSES,
TRANSACTION_TYPES,
TRANSACTION_ENVELOPE_TYPES,
TRANSACTION_EVENTS,
} from '../../../../shared/constants/transaction';
import { SECOND } from '../../../../shared/constants/time';
@ -22,7 +23,7 @@ import {
} from '../../../../shared/constants/gas';
import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../ui/helpers/constants/transactions';
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
import TransactionController, { TRANSACTION_EVENTS } from '.';
import TransactionController from '.';
const noop = () => true;
const currentNetworkId = '42';
@ -35,17 +36,21 @@ const VALID_ADDRESS = '0x0000000000000000000000000000000000000000';
const VALID_ADDRESS_TWO = '0x0000000000000000000000000000000000000001';
describe('Transaction Controller', function () {
let txController, provider, providerResultStub, fromAccount;
let txController, provider, providerResultStub, fromAccount, fragmentExists;
beforeEach(function () {
fragmentExists = false;
providerResultStub = {
// 1 gwei
eth_gasPrice: '0x0de0b6b3a7640000',
// by default, all accounts are external accounts (not contracts)
eth_getCode: '0x',
};
provider = createTestProviderTools({ scaffold: providerResultStub })
.provider;
provider = createTestProviderTools({
scaffold: providerResultStub,
networkId: currentNetworkId,
chainId: currentNetworkId,
}).provider;
fromAccount = getTestAccounts()[0];
const blockTrackerStub = new EventEmitter();
@ -70,6 +75,11 @@ describe('Transaction Controller', function () {
getCurrentChainId: () => currentChainId,
getParticipateInMetrics: () => false,
trackMetaMetricsEvent: () => undefined,
createEventFragment: () => undefined,
updateEventFragment: () => undefined,
finalizeEventFragment: () => undefined,
getEventFragmentById: () =>
fragmentExists === false ? undefined : { id: 0 },
getEIP1559GasFeeEstimates: () => undefined,
});
txController.nonceTracker.getNonceLock = () =>
@ -1536,66 +1546,325 @@ describe('Transaction Controller', function () {
describe('#_trackTransactionMetricsEvent', function () {
let trackMetaMetricsEventSpy;
let createEventFragmentSpy;
let finalizeEventFragmentSpy;
beforeEach(function () {
trackMetaMetricsEventSpy = sinon.spy(
txController,
'_trackMetaMetricsEvent',
);
createEventFragmentSpy = sinon.spy(txController, 'createEventFragment');
finalizeEventFragmentSpy = sinon.spy(
txController,
'finalizeEventFragment',
);
});
afterEach(function () {
trackMetaMetricsEventSpy.restore();
createEventFragmentSpy.restore();
finalizeEventFragmentSpy.restore();
});
it('should call _trackMetaMetricsEvent with the correct payload (user source)', function () {
const txMeta = {
id: 1,
status: TRANSACTION_STATUSES.UNAPPROVED,
txParams: {
from: fromAccount.address,
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
gasPrice: '0x77359400',
gas: '0x7b0d',
nonce: '0x4b',
},
type: TRANSACTION_TYPES.SIMPLE_SEND,
origin: 'metamask',
chainId: currentChainId,
time: 1624408066355,
metamaskNetworkId: currentNetworkId,
};
const expectedPayload = {
event: 'Transaction Added',
category: 'Transactions',
properties: {
chain_id: '0x2a',
network: '42',
referrer: 'metamask',
source: 'user',
describe('On transaction created by the user', function () {
let txMeta;
before(function () {
txMeta = {
id: 1,
status: TRANSACTION_STATUSES.UNAPPROVED,
txParams: {
from: fromAccount.address,
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
gasPrice: '0x77359400',
gas: '0x7b0d',
nonce: '0x4b',
},
type: TRANSACTION_TYPES.SIMPLE_SEND,
},
sensitiveProperties: {
gas_price: '2',
gas_limit: '0x7b0d',
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
},
};
origin: 'metamask',
chainId: currentChainId,
time: 1624408066355,
metamaskNetworkId: currentNetworkId,
};
});
txController._trackTransactionMetricsEvent(
txMeta,
TRANSACTION_EVENTS.ADDED,
);
assert.equal(trackMetaMetricsEventSpy.callCount, 1);
assert.deepEqual(
trackMetaMetricsEventSpy.getCall(0).args[0],
expectedPayload,
);
it('should create an event fragment when transaction added', function () {
const expectedPayload = {
initialEvent: 'Transaction Added',
successEvent: 'Transaction Approved',
failureEvent: 'Transaction Rejected',
uniqueIdentifier: 'transaction-added-1',
category: 'Transactions',
persist: true,
properties: {
chain_id: '0x2a',
network: '42',
referrer: 'metamask',
source: 'user',
type: TRANSACTION_TYPES.SIMPLE_SEND,
},
sensitiveProperties: {
gas_price: '2',
gas_limit: '0x7b0d',
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
},
};
txController._trackTransactionMetricsEvent(
txMeta,
TRANSACTION_EVENTS.ADDED,
);
assert.equal(createEventFragmentSpy.callCount, 1);
assert.equal(finalizeEventFragmentSpy.callCount, 0);
assert.deepEqual(
createEventFragmentSpy.getCall(0).args[0],
expectedPayload,
);
});
it('Should finalize the transaction added fragment as abandoned if user rejects transaction', function () {
fragmentExists = true;
txController._trackTransactionMetricsEvent(
txMeta,
TRANSACTION_EVENTS.REJECTED,
);
assert.equal(createEventFragmentSpy.callCount, 0);
assert.equal(finalizeEventFragmentSpy.callCount, 1);
assert.deepEqual(
finalizeEventFragmentSpy.getCall(0).args[0],
'transaction-added-1',
);
assert.deepEqual(finalizeEventFragmentSpy.getCall(0).args[1], {
abandoned: true,
});
});
it('Should finalize the transaction added fragment if user approves transaction', function () {
fragmentExists = true;
txController._trackTransactionMetricsEvent(
txMeta,
TRANSACTION_EVENTS.APPROVED,
);
assert.equal(createEventFragmentSpy.callCount, 0);
assert.equal(finalizeEventFragmentSpy.callCount, 1);
assert.deepEqual(
finalizeEventFragmentSpy.getCall(0).args[0],
'transaction-added-1',
);
assert.deepEqual(
finalizeEventFragmentSpy.getCall(0).args[1],
undefined,
);
});
it('should create an event fragment when transaction is submitted', function () {
const expectedPayload = {
initialEvent: 'Transaction Submitted',
successEvent: 'Transaction Finalized',
uniqueIdentifier: 'transaction-submitted-1',
category: 'Transactions',
persist: true,
properties: {
chain_id: '0x2a',
network: '42',
referrer: 'metamask',
source: 'user',
type: TRANSACTION_TYPES.SIMPLE_SEND,
},
sensitiveProperties: {
gas_price: '2',
gas_limit: '0x7b0d',
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
},
};
txController._trackTransactionMetricsEvent(
txMeta,
TRANSACTION_EVENTS.SUBMITTED,
);
assert.equal(createEventFragmentSpy.callCount, 1);
assert.equal(finalizeEventFragmentSpy.callCount, 0);
assert.deepEqual(
createEventFragmentSpy.getCall(0).args[0],
expectedPayload,
);
});
it('Should finalize the transaction submitted fragment when transaction finalizes', function () {
fragmentExists = true;
txController._trackTransactionMetricsEvent(
txMeta,
TRANSACTION_EVENTS.FINALIZED,
);
assert.equal(createEventFragmentSpy.callCount, 0);
assert.equal(finalizeEventFragmentSpy.callCount, 1);
assert.deepEqual(
finalizeEventFragmentSpy.getCall(0).args[0],
'transaction-submitted-1',
);
assert.deepEqual(
finalizeEventFragmentSpy.getCall(0).args[1],
undefined,
);
});
});
it('should call _trackMetaMetricsEvent with the correct payload (dapp source)', function () {
describe('On transaction suggested by dapp', function () {
let txMeta;
before(function () {
txMeta = {
id: 1,
status: TRANSACTION_STATUSES.UNAPPROVED,
txParams: {
from: fromAccount.address,
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
gasPrice: '0x77359400',
gas: '0x7b0d',
nonce: '0x4b',
},
type: TRANSACTION_TYPES.SIMPLE_SEND,
origin: 'other',
chainId: currentChainId,
time: 1624408066355,
metamaskNetworkId: currentNetworkId,
};
});
it('should create an event fragment when transaction added', function () {
const expectedPayload = {
initialEvent: 'Transaction Added',
successEvent: 'Transaction Approved',
failureEvent: 'Transaction Rejected',
uniqueIdentifier: 'transaction-added-1',
category: 'Transactions',
persist: true,
properties: {
chain_id: '0x2a',
network: '42',
referrer: 'other',
source: 'dapp',
type: TRANSACTION_TYPES.SIMPLE_SEND,
},
sensitiveProperties: {
gas_price: '2',
gas_limit: '0x7b0d',
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
},
};
txController._trackTransactionMetricsEvent(
txMeta,
TRANSACTION_EVENTS.ADDED,
);
assert.equal(createEventFragmentSpy.callCount, 1);
assert.equal(finalizeEventFragmentSpy.callCount, 0);
assert.deepEqual(
createEventFragmentSpy.getCall(0).args[0],
expectedPayload,
);
});
it('Should finalize the transaction added fragment as abandoned if user rejects transaction', function () {
fragmentExists = true;
txController._trackTransactionMetricsEvent(
txMeta,
TRANSACTION_EVENTS.REJECTED,
);
assert.equal(createEventFragmentSpy.callCount, 0);
assert.equal(finalizeEventFragmentSpy.callCount, 1);
assert.deepEqual(
finalizeEventFragmentSpy.getCall(0).args[0],
'transaction-added-1',
);
assert.deepEqual(finalizeEventFragmentSpy.getCall(0).args[1], {
abandoned: true,
});
});
it('Should finalize the transaction added fragment if user approves transaction', function () {
fragmentExists = true;
txController._trackTransactionMetricsEvent(
txMeta,
TRANSACTION_EVENTS.APPROVED,
);
assert.equal(createEventFragmentSpy.callCount, 0);
assert.equal(finalizeEventFragmentSpy.callCount, 1);
assert.deepEqual(
finalizeEventFragmentSpy.getCall(0).args[0],
'transaction-added-1',
);
assert.deepEqual(
finalizeEventFragmentSpy.getCall(0).args[1],
undefined,
);
});
it('should create an event fragment when transaction is submitted', function () {
const expectedPayload = {
initialEvent: 'Transaction Submitted',
successEvent: 'Transaction Finalized',
uniqueIdentifier: 'transaction-submitted-1',
category: 'Transactions',
persist: true,
properties: {
chain_id: '0x2a',
network: '42',
referrer: 'other',
source: 'dapp',
type: TRANSACTION_TYPES.SIMPLE_SEND,
},
sensitiveProperties: {
gas_price: '2',
gas_limit: '0x7b0d',
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
},
};
txController._trackTransactionMetricsEvent(
txMeta,
TRANSACTION_EVENTS.SUBMITTED,
);
assert.equal(createEventFragmentSpy.callCount, 1);
assert.equal(finalizeEventFragmentSpy.callCount, 0);
assert.deepEqual(
createEventFragmentSpy.getCall(0).args[0],
expectedPayload,
);
});
it('Should finalize the transaction submitted fragment when transaction finalizes', function () {
fragmentExists = true;
txController._trackTransactionMetricsEvent(
txMeta,
TRANSACTION_EVENTS.FINALIZED,
);
assert.equal(createEventFragmentSpy.callCount, 0);
assert.equal(finalizeEventFragmentSpy.callCount, 1);
assert.deepEqual(
finalizeEventFragmentSpy.getCall(0).args[0],
'transaction-submitted-1',
);
assert.deepEqual(
finalizeEventFragmentSpy.getCall(0).args[1],
undefined,
);
});
});
it('should create missing fragments when events happen out of order or are missing', function () {
const txMeta = {
id: 1,
status: TRANSACTION_STATUSES.UNAPPROVED,
@ -1612,9 +1881,13 @@ describe('Transaction Controller', function () {
time: 1624408066355,
metamaskNetworkId: currentNetworkId,
};
const expectedPayload = {
event: 'Transaction Added',
successEvent: 'Transaction Approved',
failureEvent: 'Transaction Rejected',
uniqueIdentifier: 'transaction-added-1',
category: 'Transactions',
persist: true,
properties: {
chain_id: '0x2a',
network: '42',
@ -1630,16 +1903,21 @@ describe('Transaction Controller', function () {
status: 'unapproved',
},
};
txController._trackTransactionMetricsEvent(
txMeta,
TRANSACTION_EVENTS.ADDED,
TRANSACTION_EVENTS.APPROVED,
);
assert.equal(trackMetaMetricsEventSpy.callCount, 1);
assert.equal(createEventFragmentSpy.callCount, 1);
assert.deepEqual(
trackMetaMetricsEventSpy.getCall(0).args[0],
createEventFragmentSpy.getCall(0).args[0],
expectedPayload,
);
assert.equal(finalizeEventFragmentSpy.callCount, 1);
assert.deepEqual(
finalizeEventFragmentSpy.getCall(0).args[0],
'transaction-added-1',
);
assert.deepEqual(finalizeEventFragmentSpy.getCall(0).args[1], undefined);
});
it('should call _trackMetaMetricsEvent with the correct payload (extra params)', function () {
@ -1660,7 +1938,11 @@ describe('Transaction Controller', function () {
metamaskNetworkId: currentNetworkId,
};
const expectedPayload = {
event: 'Transaction Added',
initialEvent: 'Transaction Added',
successEvent: 'Transaction Approved',
failureEvent: 'Transaction Rejected',
uniqueIdentifier: 'transaction-added-1',
persist: true,
category: 'Transactions',
properties: {
network: '42',
@ -1688,9 +1970,10 @@ describe('Transaction Controller', function () {
foo: 'bar',
},
);
assert.equal(trackMetaMetricsEventSpy.callCount, 1);
assert.equal(createEventFragmentSpy.callCount, 1);
assert.equal(finalizeEventFragmentSpy.callCount, 0);
assert.deepEqual(
trackMetaMetricsEventSpy.getCall(0).args[0],
createEventFragmentSpy.getCall(0).args[0],
expectedPayload,
);
});
@ -1716,7 +1999,11 @@ describe('Transaction Controller', function () {
metamaskNetworkId: currentNetworkId,
};
const expectedPayload = {
event: 'Transaction Added',
initialEvent: 'Transaction Added',
successEvent: 'Transaction Approved',
failureEvent: 'Transaction Rejected',
uniqueIdentifier: 'transaction-added-1',
persist: true,
category: 'Transactions',
properties: {
chain_id: '0x2a',
@ -1747,9 +2034,10 @@ describe('Transaction Controller', function () {
foo: 'bar',
},
);
assert.equal(trackMetaMetricsEventSpy.callCount, 1);
assert.equal(createEventFragmentSpy.callCount, 1);
assert.equal(finalizeEventFragmentSpy.callCount, 0);
assert.deepEqual(
trackMetaMetricsEventSpy.getCall(0).args[0],
createEventFragmentSpy.getCall(0).args[0],
expectedPayload,
);
});

@ -117,6 +117,8 @@ export default class TransactionStateManager extends EventEmitter {
time: new Date().getTime(),
status: TRANSACTION_STATUSES.UNAPPROVED,
metamaskNetworkId: netId,
originalGasEstimate: opts.txParams?.gas,
userEditedGasLimit: false,
chainId,
loadingDefaults: true,
dappSuggestedGasFees,

@ -220,22 +220,22 @@ export default class MetamaskController extends EventEmitter {
onNetworkStateChange: this.networkController.store.subscribe.bind(
this.networkController.store,
),
getAssetName: this.assetsContractController.getAssetName.bind(
getERC721AssetName: this.assetsContractController.getERC721AssetName.bind(
this.assetsContractController,
),
getAssetSymbol: this.assetsContractController.getAssetSymbol.bind(
getERC721AssetSymbol: this.assetsContractController.getERC721AssetSymbol.bind(
this.assetsContractController,
),
getCollectibleTokenURI: this.assetsContractController.getCollectibleTokenURI.bind(
getERC721TokenURI: this.assetsContractController.getERC721TokenURI.bind(
this.assetsContractController,
),
getOwnerOf: this.assetsContractController.getOwnerOf.bind(
getERC721OwnerOf: this.assetsContractController.getERC721OwnerOf.bind(
this.assetsContractController,
),
balanceOfERC1155Collectible: this.assetsContractController.balanceOfERC1155Collectible.bind(
getERC1155BalanceOf: this.assetsContractController.getERC1155BalanceOf.bind(
this.assetsContractController,
),
uriERC1155Collectible: this.assetsContractController.uriERC1155Collectible.bind(
getERC1155TokenURI: this.assetsContractController.getERC1155TokenURI.bind(
this.assetsContractController,
),
},
@ -243,6 +243,8 @@ export default class MetamaskController extends EventEmitter {
initState.CollectiblesController,
);
this.collectiblesController.setApiKey(process.env.OPENSEA_KEY);
process.env.COLLECTIBLES_V1 &&
(this.collectibleDetectionController = new CollectibleDetectionController(
{
@ -577,6 +579,18 @@ export default class MetamaskController extends EventEmitter {
),
provider: this.provider,
blockTracker: this.blockTracker,
createEventFragment: this.metaMetricsController.createEventFragment.bind(
this.metaMetricsController,
),
updateEventFragment: this.metaMetricsController.updateEventFragment.bind(
this.metaMetricsController,
),
finalizeEventFragment: this.metaMetricsController.finalizeEventFragment.bind(
this.metaMetricsController,
),
getEventFragmentById: this.metaMetricsController.getEventFragmentById.bind(
this.metaMetricsController,
),
trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind(
this.metaMetricsController,
),
@ -636,8 +650,7 @@ export default class MetamaskController extends EventEmitter {
this.collectiblesController.checkAndUpdateSingleCollectibleOwnershipStatus(
knownCollectible,
false,
// TODO add this when checkAndUpdateSingleCollectibleOwnershipStatus is updated
// { userAddress, chainId },
{ userAddress, chainId },
);
}
}
@ -1040,6 +1053,7 @@ export default class MetamaskController extends EventEmitter {
appStateController,
collectiblesController,
collectibleDetectionController,
assetsContractController,
currencyRateController,
detectTokensController,
ensController,
@ -1188,6 +1202,14 @@ export default class MetamaskController extends EventEmitter {
setAdvancedGasFee: preferencesController.setAdvancedGasFee.bind(
preferencesController,
),
setEIP1559V2Enabled: preferencesController.setEIP1559V2Enabled.bind(
preferencesController,
),
// AssetsContractController
getTokenStandardAndDetails: assetsContractController.getTokenStandardAndDetails.bind(
assetsContractController,
),
// CollectiblesController
addCollectible: collectiblesController.addCollectible.bind(
@ -1246,6 +1268,12 @@ export default class MetamaskController extends EventEmitter {
setCollectiblesDetectionNoticeDismissed: appStateController.setCollectiblesDetectionNoticeDismissed.bind(
appStateController,
),
setEnableEIP1559V2NoticeDismissed: appStateController.setEnableEIP1559V2NoticeDismissed.bind(
appStateController,
),
updateCollectibleDropDownState: appStateController.updateCollectibleDropDownState.bind(
appStateController,
),
// EnsController
tryReverseResolveAddress: ensController.reverseResolveAddress.bind(
ensController,
@ -1270,6 +1298,9 @@ export default class MetamaskController extends EventEmitter {
addUnapprovedTransaction: txController.addUnapprovedTransaction.bind(
txController,
),
createTransactionEventFragment: txController.createTransactionEventFragment.bind(
txController,
),
// messageManager
signMessage: this.signMessage.bind(this),
@ -1402,6 +1433,15 @@ export default class MetamaskController extends EventEmitter {
trackMetaMetricsPage: metaMetricsController.trackPage.bind(
metaMetricsController,
),
createEventFragment: metaMetricsController.createEventFragment.bind(
metaMetricsController,
),
updateEventFragment: metaMetricsController.updateEventFragment.bind(
metaMetricsController,
),
finalizeEventFragment: metaMetricsController.finalizeEventFragment.bind(
metaMetricsController,
),
// approval controller
resolvePendingApproval: approvalController.accept.bind(

@ -200,9 +200,19 @@ const directiveParsingRegex = /^([A-Z]+):([A-Z_]+)(?:\(((?:\w+,)*\w+)\))?$/u;
* a boolean indicating whether they were modified.
*/
function removeFencedCode(filePath, typeOfCurrentBuild, fileContent) {
const matchedLines = [...fileContent.matchAll(linesWithFenceRegex)];
// Do not modify the file if we detect an inline sourcemap. For reasons
// yet to be determined, the transform receives every file twice while in
// watch mode, the second after Babel has transpiled the file. Babel adds
// inline source maps to the file, something we will never do in our own
// source files, so we use the existence of inline source maps to determine
// whether we should ignore the file.
if (/^\/\/# sourceMappingURL=/gmu.test(fileContent)) {
return [fileContent, false];
}
// If we didn't match any lines, return the unmodified file contents.
const matchedLines = [...fileContent.matchAll(linesWithFenceRegex)];
if (matchedLines.length === 0) {
return [fileContent, false];
}

@ -610,6 +610,17 @@ describe('build/transforms/remove-fenced-code', () => {
});
});
it('ignores files with inline source maps', () => {
// This is so that there isn't an unnecessary second execution of
// removeFencedCode with a transpiled version of the same file
const input = getTestData().validInputs.extraContentWithFences.concat(
'\n//# sourceMappingURL=as32e32wcwc2234f2ew32cnin4243f4nv9nsdoivnxzoivnd',
);
expect(
removeFencedCode(mockFileName, BuildType.flask, input),
).toStrictEqual([input, false]);
});
// We can't do this until there's more than one command
it.todo('rejects directive pairs with mismatched commands');
});

@ -4,7 +4,7 @@ set -e
set -u
set -o pipefail
ganache_cli="$(yarn bin)/ganache-cli"
ganache_cli="$(yarn bin)/ganache"
seed_phrase="${GANACHE_SEED_PHRASE:-phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent}"
_term () {
@ -23,7 +23,7 @@ trap _term SIGTERM
trap _int SIGINT
# shellcheck disable=SC2086
$ganache_cli --noVMErrorsOnRPCResponse --networkId 1337 --mnemonic "$seed_phrase" ${GANACHE_ARGS:-} &
$ganache_cli --chain.vmErrorsOnRPCResponse false --networkId 1337 --mnemonic "$seed_phrase" ${GANACHE_ARGS:-} &
child=$!
wait "$child"

@ -955,7 +955,10 @@
},
"abstract-leveldown": {
"packages": {
"buffer": true,
"immediate": true,
"is-buffer": true,
"level-supports": true,
"process": true,
"xtend": true
}
@ -3042,6 +3045,11 @@
"xtend": true
}
},
"level-supports": {
"packages": {
"xtend": true
}
},
"levelup": {
"packages": {
"assert": true,

@ -955,7 +955,10 @@
},
"abstract-leveldown": {
"packages": {
"buffer": true,
"immediate": true,
"is-buffer": true,
"level-supports": true,
"process": true,
"xtend": true
}
@ -3042,6 +3045,11 @@
"xtend": true
}
},
"level-supports": {
"packages": {
"xtend": true
}
},
"levelup": {
"packages": {
"assert": true,

@ -955,7 +955,10 @@
},
"abstract-leveldown": {
"packages": {
"buffer": true,
"immediate": true,
"is-buffer": true,
"level-supports": true,
"process": true,
"xtend": true
}
@ -3042,6 +3045,11 @@
"xtend": true
}
},
"level-supports": {
"packages": {
"xtend": true
}
},
"levelup": {
"packages": {
"assert": true,

@ -108,9 +108,9 @@
"@keystonehq/metamask-airgapped-keyring": "0.2.1",
"@material-ui/core": "^4.11.0",
"@metamask/contract-metadata": "^1.31.0",
"@metamask/controllers": "^24.0.0",
"@metamask/controllers": "^25.0.0",
"@metamask/eth-ledger-bridge-keyring": "^0.10.0",
"@metamask/eth-token-tracker": "^3.0.1",
"@metamask/eth-token-tracker": "^4.0.0",
"@metamask/etherscan-link": "^2.1.0",
"@metamask/jazzicon": "^2.0.0",
"@metamask/logo": "^3.1.1",
@ -291,8 +291,7 @@
"fancy-log": "^1.3.3",
"fast-glob": "^3.2.2",
"fs-extra": "^8.1.0",
"ganache-cli": "^6.12.1",
"ganache-core": "^2.13.1",
"ganache": "^v7.0.0-rc.0",
"geckodriver": "^1.21.0",
"globby": "^11.0.4",
"gulp": "^4.0.2",
@ -386,7 +385,8 @@
"node-hid": false,
"usb": false,
"blake-hash": false,
"protobufjs": false
"protobufjs": false,
"@trufflesuite/bigint-buffer": false
}
}
}

@ -17,6 +17,13 @@ export const DEVICE_NAMES = {
LATTICE: 'lattice',
};
export const KEYRING_NAMES = {
LEDGER: 'Ledger',
TREZOR: 'Trezor',
QR: 'QR',
LATTICE: 'Lattice1',
};
/**
* Used for setting the users preference for ledger transport type
*/

@ -82,6 +82,46 @@
* segment source that marks the event data as not conforming to our schema
*/
/**
* @typedef {Object} MetaMetricsEventFragment
* @property {string} successEvent - The event name to fire when the fragment
* is closed in an affirmative action.
* @property {string} [failureEvent] - The event name to fire when the fragment
* is closed with a rejection.
* @property {string} [initialEvent] - An event name to fire immediately upon
* fragment creation. This is useful for building funnels in mixpanel and for
* reduction of code duplication.
* @property {string} category - the event category to use for both the success
* and failure events
* @property {boolean} [persist] - Should this fragment be persisted in
* state and progressed after the extension is locked and unlocked.
* @property {number} [timeout] - Time in seconds the event should be persisted
* for. After the timeout the fragment will be closed as abandoned. if not
* supplied the fragment is stored indefinitely.
* @property {number} [lastUpdated] - Date.now() when the fragment was last
* updated. Used to determine if the timeout has expired and the fragment
* should be closed.
* @property {object} [properties] - Object of custom values to track, keys in
* this object must be in snake_case.
* @property {object} [sensitiveProperties] - Object of sensitive values to
* track. Keys in this object must be in snake_case. These properties will be
* sent in an additional event that excludes the user's metaMetricsId
* @property {number} [revenue] - amount of currency that event creates in
* revenue for MetaMask if fragment is successful.
* @property {string} [currency] - ISO 4127 format currency for events with
* revenue, defaults to US dollars
* @property {number} [value] - Abstract business "value" attributable to
* customers who successfully complete this fragment
* @property {MetaMetricsPageObject} [page] - the page/route that the event
* occurred on
* @property {MetaMetricsReferrerObject} [referrer] - the origin of the dapp
* that initiated the event fragment.
* @property {string} [uniqueIdentifier] - optional argument to override the
* automatic generation of UUID for the event fragment. This is useful when
* tracking events for subsystems that already generate UUIDs so to avoid
* unnecessary lookups and reduce accidental duplication.
*/
/**
* Represents the shape of data sent to the segment.track method.
*

@ -226,6 +226,10 @@ export const TRANSACTION_GROUP_CATEGORIES = {
* TransactionMeta object.
* @property {string} origin - A string representing the interface that
* suggested the transaction.
* @property {string} originalGasEstimate - A string representing the original
* gas estimation on the transaction metadata.
* @property {boolean} userEditedGasLimit - A boolean representing when the
* user manually edited the gas limit.
* @property {Object} nonceDetails - A metadata object containing information
* used to derive the suggested nonce, useful for debugging nonce issues.
* @property {string} rawTx - A hex string of the final signed transaction,
@ -236,3 +240,49 @@ export const TRANSACTION_GROUP_CATEGORIES = {
* the network, in Unix epoch time (ms).
* @property {TxError} [err] - The error encountered during the transaction
*/
/**
* Defines the possible types
*
* @typedef {Object} TransactionMetaMetricsEvents
* @property {'Transaction Added'} ADDED - All transactions, except incoming
* ones, are added to the controller state in an unapproved status. When this
* happens we fire the Transaction Added event to show that the transaction
* has been added to the user's MetaMask.
* @property {'Transaction Approved'} APPROVED - When an unapproved transaction
* is in the controller state, MetaMask will render a confirmation screen for
* that transaction. If the user approves the transaction we fire this event
* to indicate that the user has approved the transaction for submission to
* the network.
* @property {'Transaction Rejected'} REJECTED - When an unapproved transaction
* is in the controller state, MetaMask will render a confirmation screen for
* that transaction. If the user rejects the transaction we fire this event
* to indicate that the user has rejected the transaction. It will be removed
* from state as a result.
* @property {'Transaction Submitted'} SUBMITTED - After a transaction is
* approved by the user, it is then submitted to the network for inclusion in
* a block. When this happens we fire the Transaction Submitted event to
* indicate that MetaMask is submitting a transaction at the user's request.
* @property {'Transaction Finalized'} FINALIZED - All transactions that are
* submitted will finalized (eventually) by either being dropped, failing
* or being confirmed. When this happens we track this event, along with the
* status.
*/
/**
* This type will work anywhere you expect a string that can be one of the
* above transaction event types.
*
* @typedef {TransactionMetaMetricsEvents[keyof TransactionMetaMetricsEvents]} TransactionMetaMetricsEventString
*/
/**
* @type {TransactionMetaMetricsEvents}
*/
export const TRANSACTION_EVENTS = {
ADDED: 'Transaction Added',
APPROVED: 'Transaction Approved',
FINALIZED: 'Transaction Finalized',
REJECTED: 'Transaction Rejected',
SUBMITTED: 'Transaction Submitted',
};

@ -0,0 +1,145 @@
{
"data": {
"AppStateController": {
"mkrMigrationReminderTimestamp": null,
"swapsWelcomeMessageHasBeenShown": true
},
"CachedBalancesController": {
"cachedBalances": {
"4": {}
}
},
"CurrencyController": {
"conversionDate": 1575697244.188,
"conversionRate": 149.61,
"currentCurrency": "usd",
"nativeCurrency": "ETH"
},
"IncomingTransactionsController": {
"incomingTransactions": {},
"incomingTxLastFetchedBlocksByNetwork": {
"goerli": null,
"kovan": null,
"mainnet": null,
"rinkeby": 5570536
}
},
"KeyringController": {
"vault": "{\"data\":\"s6TpYjlUNsn7ifhEFTkuDGBUM1GyOlPrim7JSjtfIxgTt8/6MiXgiR/CtFfR4dWW2xhq85/NGIBYEeWrZThGdKGarBzeIqBfLFhw9n509jprzJ0zc2Rf+9HVFGLw+xxC4xPxgCS0IIWeAJQ+XtGcHmn0UZXriXm8Ja4kdlow6SWinB7sr/WM3R0+frYs4WgllkwggDf2/Tv6VHygvLnhtzp6hIJFyTjh+l/KnyJTyZW1TkZhDaNDzX3SCOHT\",\"iv\":\"FbeHDAW5afeWNORfNJBR0Q==\",\"salt\":\"TxZ+WbCW6891C9LK/hbMAoUsSEW1E8pyGLVBU6x5KR8=\"}"
},
"NetworkController": {
"network": "1337",
"provider": {
"nickname": "Localhost 8545",
"rpcUrl": "http://localhost:8545",
"chainId": "0x539",
"ticker": "ETH",
"type": "rpc"
}
},
"NotificationController": {
"notifications": {
"1": {
"isShown": true
},
"3": {
"isShown": true
},
"5": {
"isShown": true
},
"6": {
"isShown": true
},
"8": {
"isShown": true
}
}
},
"OnboardingController": {
"onboardingTabs": {},
"seedPhraseBackedUp": false
},
"PermissionsMetadata": {
"domainMetadata": {
"metamask.github.io": {
"icon": null,
"name": "M E T A M A S K M E S H T E S T"
}
},
"permissionsHistory": {},
"permissionsLog": [
{
"id": 746677923,
"method": "eth_accounts",
"methodType": "restricted",
"origin": "metamask.github.io",
"request": {
"id": 746677923,
"jsonrpc": "2.0",
"method": "eth_accounts",
"origin": "metamask.github.io",
"params": []
},
"requestTime": 1575697241368,
"response": {
"id": 746677923,
"jsonrpc": "2.0",
"result": []
},
"responseTime": 1575697241370,
"success": true
}
]
},
"PreferencesController": {
"accountTokens": {
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": {
"rinkeby": [],
"ropsten": []
}
},
"assetImages": {},
"completedOnboarding": true,
"eip1559V2Enabled": true,
"currentLocale": "en",
"featureFlags": {
"showIncomingTransactions": true,
"transactionTime": false
},
"firstTimeFlowType": "create",
"forgottenPassword": false,
"frequentRpcListDetail": [],
"identities": {
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": {
"address": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1",
"name": "Account 1"
}
},
"knownMethodData": {},
"lostIdentities": {},
"metaMetricsId": null,
"participateInMetaMetrics": false,
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true
},
"selectedAddress": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1",
"suggestedTokens": {},
"tokens": [],
"useBlockie": false,
"useNonceField": false,
"usePhishDetect": true
},
"TransactionController": {
"transactions": {}
},
"config": {},
"firstTimeInfo": {
"date": 1575697234195,
"version": "7.7.0"
}
},
"meta": {
"version": 40
}
}

@ -0,0 +1,214 @@
{
"data": {
"AppStateController": {
"mkrMigrationReminderTimestamp": null,
"swapsWelcomeMessageHasBeenShown": true
},
"CachedBalancesController": {
"cachedBalances": {
"4": {}
}
},
"CurrencyController": {
"conversionDate": 1575697244.188,
"conversionRate": 149.61,
"currentCurrency": "usd",
"nativeCurrency": "ETH"
},
"IncomingTransactionsController": {
"incomingTransactions": {},
"incomingTxLastFetchedBlocksByNetwork": {
"goerli": null,
"kovan": null,
"mainnet": null,
"rinkeby": 5570536
}
},
"KeyringController": {
"vault": "{\"data\":\"s6TpYjlUNsn7ifhEFTkuDGBUM1GyOlPrim7JSjtfIxgTt8/6MiXgiR/CtFfR4dWW2xhq85/NGIBYEeWrZThGdKGarBzeIqBfLFhw9n509jprzJ0zc2Rf+9HVFGLw+xxC4xPxgCS0IIWeAJQ+XtGcHmn0UZXriXm8Ja4kdlow6SWinB7sr/WM3R0+frYs4WgllkwggDf2/Tv6VHygvLnhtzp6hIJFyTjh+l/KnyJTyZW1TkZhDaNDzX3SCOHT\",\"iv\":\"FbeHDAW5afeWNORfNJBR0Q==\",\"salt\":\"TxZ+WbCW6891C9LK/hbMAoUsSEW1E8pyGLVBU6x5KR8=\"}"
},
"NetworkController": {
"network": "1337",
"provider": {
"nickname": "Localhost 8545",
"rpcUrl": "http://localhost:8545",
"chainId": "0x539",
"ticker": "ETH",
"type": "rpc"
}
},
"NotificationController": {
"notifications": {
"1": {
"isShown": true
},
"3": {
"isShown": true
},
"5": {
"isShown": true
},
"6": {
"isShown": true
},
"8": {
"isShown": true
}
}
},
"OnboardingController": {
"onboardingTabs": {},
"seedPhraseBackedUp": false
},
"PermissionsMetadata": {
"domainMetadata": {
"metamask.github.io": {
"icon": null,
"name": "M E T A M A S K M E S H T E S T"
}
},
"permissionsHistory": {},
"permissionsLog": [
{
"id": 746677923,
"method": "eth_accounts",
"methodType": "restricted",
"origin": "metamask.github.io",
"request": {
"id": 746677923,
"jsonrpc": "2.0",
"method": "eth_accounts",
"origin": "metamask.github.io",
"params": []
},
"requestTime": 1575697241368,
"response": {
"id": 746677923,
"jsonrpc": "2.0",
"result": []
},
"responseTime": 1575697241370,
"success": true
}
]
},
"PreferencesController": {
"accountTokens": {
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": {
"rinkeby": [],
"ropsten": []
}
},
"assetImages": {},
"completedOnboarding": true,
"eip1559V2Enabled": true,
"currentLocale": "en",
"featureFlags": {
"showIncomingTransactions": true,
"transactionTime": false
},
"firstTimeFlowType": "create",
"forgottenPassword": false,
"frequentRpcListDetail": [],
"identities": {
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": {
"address": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1",
"name": "Account 1"
}
},
"knownMethodData": {},
"lostIdentities": {},
"metaMetricsId": null,
"participateInMetaMetrics": false,
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true
},
"selectedAddress": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1",
"suggestedTokens": {},
"tokens": [],
"useBlockie": false,
"useNonceField": false,
"usePhishDetect": true
},
"TransactionController": {
"transactions": {
"4046084157914634": {
"chainId": "0x539",
"primaryTransaction": {
"chainId": "0x539",
"id": 4046084157914634,
"loadingDefaults": true,
"metamaskNetworkId": "1337",
"origin": "metamask",
"status": "unapproved",
"time": 1617228030067,
"txParams": {
"from": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1",
"gas": "0x61a8",
"maxFeePerGas": "0x59682f0c",
"maxPriorityFeePerGas": "0x59682f00",
"type": "0x2",
"to": "0x2f318C334780961FB129D2a6c30D0763d9a5C970",
"value": "0x1e87F85809dc0000"
},
"type": "sentEther"
},
"history": [
{
"chainId": "0x539",
"id": 4046084157914634,
"loadingDefaults": true,
"metamaskNetworkId": "1337",
"origin": "metamask",
"status": "unapproved",
"time": 1617228030067,
"txParams": {
"from": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1",
"gas": "0x61a8",
"maxFeePerGas": "0x59682f0c",
"maxPriorityFeePerGas": "0x59682f00",
"type": "0x2",
"to": "0x2f318C334780961FB129D2a6c30D0763d9a5C970",
"value": "0x1e87F85809dc0000"
},
"type": "simpleSend"
},
[
{
"note": "Added new unapproved transaction.",
"op": "replace",
"path": "/loadingDefaults",
"timestamp": 1617228030069,
"value": false
}
]
],
"id": 4046084157914634,
"loadingDefaults": false,
"metamaskNetworkId": "1337",
"origin": "metamask",
"status": "unapproved",
"time": 1617228030067,
"txParams": {
"from": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1",
"gas": "0x61a8",
"maxFeePerGas": "0x59682f0c",
"maxPriorityFeePerGas": "0x59682f00",
"type": "0x2",
"to": "0x2f318C334780961FB129D2a6c30D0763d9a5C970",
"value": "0x1e87F85809dc0000"
},
"type": "simpleSend"
}
}
},
"config": {},
"firstTimeInfo": {
"date": 1575697234195,
"version": "7.7.0"
}
},
"meta": {
"version": 40
}
}

@ -485,6 +485,46 @@
"usePhishDetect": true,
"useTokenDetection": true
},
"MetaMetricsController": {
"fragments": {
"transaction-added-7911313280012623": {
"category": "Transactions",
"initialEvent": "Transaction Added",
"successEvent": "Transaction Approved",
"failureEvent": "Transaction Rejected",
"properties": {},
"persist": true,
"uniqueIdentifier": "transaction-added-7911313280012623"
},
"transaction-added-7911313280012624": {
"category": "Transactions",
"initialEvent": "Transaction Added",
"successEvent": "Transaction Approved",
"failureEvent": "Transaction Rejected",
"properties": {},
"persist": true,
"uniqueIdentifier": "transaction-added-7911313280012624"
},
"transaction-added-7911313280012625": {
"category": "Transactions",
"initialEvent": "Transaction Added",
"successEvent": "Transaction Approved",
"failureEvent": "Transaction Rejected",
"properties": {},
"persist": true,
"uniqueIdentifier": "transaction-added-7911313280012625"
},
"transaction-added-7911313280012626": {
"category": "Transactions",
"initialEvent": "Transaction Added",
"successEvent": "Transaction Approved",
"failureEvent": "Transaction Rejected",
"properties": {},
"persist": true,
"uniqueIdentifier": "transaction-added-7911313280012626"
}
}
},
"TransactionController": {
"transactions": {
"7911313280012623": {

@ -0,0 +1,214 @@
{
"data": {
"AppStateController": {
"mkrMigrationReminderTimestamp": null,
"swapsWelcomeMessageHasBeenShown": true
},
"CachedBalancesController": {
"cachedBalances": {
"4": {}
}
},
"CurrencyController": {
"conversionDate": 1575697244.188,
"conversionRate": 149.61,
"currentCurrency": "usd",
"nativeCurrency": "ETH"
},
"IncomingTransactionsController": {
"incomingTransactions": {},
"incomingTxLastFetchedBlocksByNetwork": {
"goerli": null,
"kovan": null,
"mainnet": null,
"rinkeby": 5570536
}
},
"KeyringController": {
"vault": "{\"data\":\"s6TpYjlUNsn7ifhEFTkuDGBUM1GyOlPrim7JSjtfIxgTt8/6MiXgiR/CtFfR4dWW2xhq85/NGIBYEeWrZThGdKGarBzeIqBfLFhw9n509jprzJ0zc2Rf+9HVFGLw+xxC4xPxgCS0IIWeAJQ+XtGcHmn0UZXriXm8Ja4kdlow6SWinB7sr/WM3R0+frYs4WgllkwggDf2/Tv6VHygvLnhtzp6hIJFyTjh+l/KnyJTyZW1TkZhDaNDzX3SCOHT\",\"iv\":\"FbeHDAW5afeWNORfNJBR0Q==\",\"salt\":\"TxZ+WbCW6891C9LK/hbMAoUsSEW1E8pyGLVBU6x5KR8=\"}"
},
"NetworkController": {
"network": "1337",
"provider": {
"nickname": "Localhost 8545",
"rpcUrl": "http://localhost:8545",
"chainId": "0x539",
"ticker": "ETH",
"type": "rpc"
}
},
"NotificationController": {
"notifications": {
"1": {
"isShown": true
},
"3": {
"isShown": true
},
"5": {
"isShown": true
},
"6": {
"isShown": true
},
"8": {
"isShown": true
}
}
},
"OnboardingController": {
"onboardingTabs": {},
"seedPhraseBackedUp": false
},
"PermissionsMetadata": {
"domainMetadata": {
"metamask.github.io": {
"icon": null,
"name": "M E T A M A S K M E S H T E S T"
}
},
"permissionsHistory": {},
"permissionsLog": [
{
"id": 746677923,
"method": "eth_accounts",
"methodType": "restricted",
"origin": "metamask.github.io",
"request": {
"id": 746677923,
"jsonrpc": "2.0",
"method": "eth_accounts",
"origin": "metamask.github.io",
"params": []
},
"requestTime": 1575697241368,
"response": {
"id": 746677923,
"jsonrpc": "2.0",
"result": []
},
"responseTime": 1575697241370,
"success": true
}
]
},
"PreferencesController": {
"accountTokens": {
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": {
"rinkeby": [],
"ropsten": []
}
},
"assetImages": {},
"completedOnboarding": true,
"eip1559V2Enabled": true,
"currentLocale": "en",
"featureFlags": {
"showIncomingTransactions": true,
"transactionTime": false
},
"firstTimeFlowType": "create",
"forgottenPassword": false,
"frequentRpcListDetail": [],
"identities": {
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": {
"address": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1",
"name": "Account 1"
}
},
"knownMethodData": {},
"lostIdentities": {},
"metaMetricsId": null,
"participateInMetaMetrics": false,
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true
},
"selectedAddress": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1",
"suggestedTokens": {},
"tokens": [],
"useBlockie": false,
"useNonceField": false,
"usePhishDetect": true
},
"TransactionController": {
"transactions": {
"4046084157914634": {
"chainId": "0x539",
"primaryTransaction": {
"chainId": "0x539",
"id": 4046084157914634,
"loadingDefaults": true,
"metamaskNetworkId": "1337",
"origin": "metamask",
"status": "unapproved",
"time": 1617228030067,
"txParams": {
"from": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1",
"gas": "0x61a8",
"maxFeePerGas": "0x59682f0c",
"maxPriorityFeePerGas": "0x59682f00",
"type": "0x2",
"to": "0x2f318C334780961FB129D2a6c30D0763d9a5C970",
"value": "0xde0b6b3a7640000"
},
"type": "sentEther"
},
"history": [
{
"chainId": "0x539",
"id": 4046084157914634,
"loadingDefaults": true,
"metamaskNetworkId": "1337",
"origin": "metamask",
"status": "unapproved",
"time": 1617228030067,
"txParams": {
"from": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1",
"gas": "0x61a8",
"maxFeePerGas": "0x59682f0c",
"maxPriorityFeePerGas": "0x59682f00",
"type": "0x2",
"to": "0x2f318C334780961FB129D2a6c30D0763d9a5C970",
"value": "0xde0b6b3a7640000"
},
"type": "simpleSend"
},
[
{
"note": "Added new unapproved transaction.",
"op": "replace",
"path": "/loadingDefaults",
"timestamp": 1617228030069,
"value": false
}
]
],
"id": 4046084157914634,
"loadingDefaults": false,
"metamaskNetworkId": "1337",
"origin": "metamask",
"status": "unapproved",
"time": 1617228030067,
"txParams": {
"from": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1",
"gas": "0x61a8",
"maxFeePerGas": "0x59682f0c",
"maxPriorityFeePerGas": "0x59682f00",
"type": "0x2",
"to": "0x2f318C334780961FB129D2a6c30D0763d9a5C970",
"value": "0xde0b6b3a7640000"
},
"type": "simpleSend"
}
}
},
"config": {},
"firstTimeInfo": {
"date": 1575697234195,
"version": "7.7.0"
}
},
"meta": {
"version": 40
}
}

@ -129,6 +129,19 @@
"useNonceField": false,
"usePhishDetect": true
},
"MetaMetricsController": {
"fragments": {
"transaction-added-4046084157914634": {
"category": "Transactions",
"initialEvent": "Transaction Added",
"successEvent": "Transaction Approved",
"failureEvent": "Transaction Rejected",
"properties": {},
"persist": true,
"uniqueIdentifier": "transaction-added-4046084157914634"
}
}
},
"TransactionController": {
"transactions": {
"4046084157914634": {

@ -1,5 +1,4 @@
const { promisify } = require('util');
const ganache = require('ganache-core');
const ganache = require('ganache');
const defaultOptions = {
blockTime: 2,
@ -8,6 +7,7 @@ const defaultOptions = {
'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent',
port: 8545,
vmErrorsOnRPCResponse: false,
hardfork: 'muirGlacier',
};
class Ganache {
@ -15,22 +15,14 @@ class Ganache {
const options = { ...defaultOptions, ...opts };
const { port } = options;
this._server = ganache.server(options);
const listen = promisify(this._server.listen).bind(this._server);
const blockchain = await listen(port);
return {
...blockchain,
port,
};
await this._server.listen(port);
}
async quit() {
if (!this._server) {
throw new Error('Server not running yet');
}
const close = promisify(this._server.close).bind(this._server);
await close();
await this._server.close();
}
}

@ -1,5 +1,6 @@
const path = require('path');
const sinon = require('sinon');
const BigNumber = require('bignumber.js');
const createStaticServer = require('../../development/create-static-server');
const {
createSegmentServer,
@ -12,9 +13,10 @@ const { ensureXServerIsRunning } = require('./x-server');
const tinyDelayMs = 200;
const regularDelayMs = tinyDelayMs * 2;
const largeDelayMs = regularDelayMs * 2;
const dappPort = 8080;
const convertToHexValue = (val) => `0x${new BigNumber(val, 10).toString(16)}`;
async function withFixtures(options, testSuite) {
const {
dapp,
@ -42,7 +44,7 @@ async function withFixtures(options, testSuite) {
secondaryGanacheServer = new Ganache();
await secondaryGanacheServer.start({
blockTime: 2,
_chainIdRpc: chainId,
chain: { chainId },
port,
vmErrorsOnRPCResponse: false,
});
@ -147,9 +149,55 @@ async function withFixtures(options, testSuite) {
}
}
/**
* @param {*} driver - selinium driver
* @param {*} handlesCount - total count of windows that should be loaded
* @returns handles - an object with window handles, properties in object represent windows:
* 1. extension: metamask extension window
* 2. dapp: test-app window
* 3. popup: metsmask extension popup window
*/
const getWindowHandles = async (driver, handlesCount) => {
await driver.waitUntilXWindowHandles(handlesCount);
const windowHandles = await driver.getAllWindowHandles();
const extension = windowHandles[0];
const dapp = await driver.switchToWindowWithTitle(
'E2E Test Dapp',
windowHandles,
);
const popup = windowHandles.find(
(handle) => handle !== extension && handle !== dapp,
);
return { extension, dapp, popup };
};
const connectDappWithExtensionPopup = async (driver) => {
await driver.openNewPage(`http://127.0.0.1:${dappPort}/`);
await driver.delay(regularDelayMs);
await driver.clickElement({ text: 'Connect', tag: 'button' });
await driver.delay(regularDelayMs);
const windowHandles = await getWindowHandles(driver, 3);
// open extension popup and confirm connect
await driver.switchToWindow(windowHandles.popup);
await driver.delay(largeDelayMs);
await driver.clickElement({ text: 'Next', tag: 'button' });
await driver.clickElement({ text: 'Connect', tag: 'button' });
// send from dapp
await driver.waitUntilXWindowHandles(2);
await driver.switchToWindow(windowHandles.dapp);
await driver.delay(regularDelayMs);
};
module.exports = {
getWindowHandles,
convertToHexValue,
tinyDelayMs,
regularDelayMs,
largeDelayMs,
withFixtures,
connectDappWithExtensionPopup,
};

@ -217,7 +217,7 @@ describe('MetaMask', function () {
it('balance renders', async function () {
await driver.waitForSelector({
css: '[data-testid="wallet-balance"] .list-item__heading',
text: '100 ETH',
text: '1000',
});
await driver.delay(regularDelayMs);
});

@ -1,6 +1,6 @@
const { strict: assert } = require('assert');
const waitUntilCalled = require('../lib/wait-until-called');
const { withFixtures, tinyDelayMs } = require('./helpers');
const { convertToHexValue, withFixtures, tinyDelayMs } = require('./helpers');
/**
* WARNING: These tests must be run using a build created with `yarn build:test:metrics`, so that it has
@ -15,7 +15,7 @@ describe('Segment metrics', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -12,9 +12,6 @@ sendButton.addEventListener('click', function () {
gasPrice: '0x09184e72a000',
gasLimit: '0x22710',
value: '0xde0b6b3a7640000',
r: '0x25a1bc499cd8799a2ece0fcba0df6e666e54a6e2b4e18c09838e2b621c10db71',
s: '0x6cf83e6e8f6e82a0a1d7bd10bc343fc0ae4b096c1701aa54e6389d447f98ac6f',
v: '0x2d46',
to: document.getElementById('address').value,
}
var tx = new Tx(rawTx);

@ -1,5 +1,5 @@
const { strict: assert } = require('assert');
const { withFixtures } = require('../helpers');
const { convertToHexValue, withFixtures } = require('../helpers');
describe('Show account details', function () {
const ganacheOptions = {
@ -7,7 +7,7 @@ describe('Show account details', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,5 +1,5 @@
const { strict: assert } = require('assert');
const { withFixtures } = require('../helpers');
const { convertToHexValue, withFixtures } = require('../helpers');
describe('Add account', function () {
const ganacheOptions = {
@ -7,7 +7,7 @@ describe('Add account', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,5 +1,5 @@
const { strict: assert } = require('assert');
const { withFixtures } = require('../helpers');
const { convertToHexValue, withFixtures } = require('../helpers');
describe('Hide token', function () {
const ganacheOptions = {
@ -7,7 +7,7 @@ describe('Hide token', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};
@ -61,7 +61,7 @@ describe('Add existing token using search', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,5 +1,5 @@
const { strict: assert } = require('assert');
const { withFixtures } = require('../helpers');
const { convertToHexValue, withFixtures } = require('../helpers');
describe('Address Book', function () {
const ganacheOptions = {
@ -7,7 +7,7 @@ describe('Address Book', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,5 +1,9 @@
const { strict: assert } = require('assert');
const { withFixtures, regularDelayMs } = require('../helpers');
const {
convertToHexValue,
withFixtures,
regularDelayMs,
} = require('../helpers');
describe('Deploy contract and call contract methods', function () {
let windowHandles;
@ -11,7 +15,7 @@ describe('Deploy contract and call contract methods', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,5 +1,5 @@
const { strict: assert } = require('assert');
const { withFixtures } = require('../helpers');
const { convertToHexValue, withFixtures } = require('../helpers');
describe('Stores custom RPC history', function () {
const ganacheOptions = {
@ -7,7 +7,7 @@ describe('Stores custom RPC history', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -0,0 +1,277 @@
const { strict: assert } = require('assert');
const {
convertToHexValue,
connectDappWithExtensionPopup,
getWindowHandles,
largeDelayMs,
withFixtures,
regularDelayMs,
} = require('../helpers');
if (process.env.EIP_1559_V2 === '1') {
describe('Editing Confirm Transaction', function () {
it('allows selecting high, medium, low gas estimates on edit gas fee popover', async function () {
const ganacheOptions = {
hardfork: 'london',
accounts: [
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: convertToHexValue(25000000000000000000),
},
],
};
await withFixtures(
{
fixtures: 'eip-1559-v2',
ganacheOptions,
title: this.test.title,
},
async ({ driver }) => {
await driver.navigate();
await driver.fill('#password', 'correct horse battery staple');
await driver.press('#password', driver.Key.ENTER);
const transactionAmounts = await driver.findElements(
'.currency-display-component__text',
);
const transactionAmount = transactionAmounts[0];
assert.equal(await transactionAmount.getText(), '2.2');
// update estimates to high
await driver.clickElement('[data-testid="edit-gas-fee-button"]');
await driver.delay(regularDelayMs);
await driver.clickElement('[data-testid="edit-gas-fee-item-high"]');
await driver.delay(regularDelayMs);
await driver.waitForSelector({ text: '🦍' });
await driver.waitForSelector({
text: 'Aggressive',
});
// update estimates to medium
await driver.clickElement('[data-testid="edit-gas-fee-button"]');
await driver.delay(regularDelayMs);
await driver.clickElement('[data-testid="edit-gas-fee-item-medium"]');
await driver.delay(regularDelayMs);
await driver.waitForSelector({ text: '🦊' });
await driver.waitForSelector({
text: 'Market',
});
// update estimates to low
await driver.clickElement('[data-testid="edit-gas-fee-button"]');
await driver.delay(regularDelayMs);
await driver.clickElement('[data-testid="edit-gas-fee-item-low"]');
await driver.delay(regularDelayMs);
await driver.waitForSelector({ text: '🐢' });
await driver.waitForSelector({
text: 'Low',
});
await driver.waitForSelector('[data-testid="low-gas-fee-alert"]');
// confirms the transaction
await driver.clickElement({ text: 'Confirm', tag: 'button' });
await driver.delay(regularDelayMs);
await driver.clickElement('[data-testid="home__activity-tab"]');
await driver.wait(async () => {
const confirmedTxes = await driver.findElements(
'.transaction-list__completed-transactions .transaction-list-item',
);
return confirmedTxes.length === 1;
}, 10000);
const txValues = await driver.findElements(
'.transaction-list-item__primary-currency',
);
assert.equal(txValues.length, 1);
assert.ok(/-2.2\s*ETH/u.test(await txValues[0].getText()));
},
);
});
it('allows accessing advance gas fee popover from edit gas fee popover', async function () {
const ganacheOptions = {
hardfork: 'london',
accounts: [
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: convertToHexValue(25000000000000000000),
},
],
};
await withFixtures(
{
fixtures: 'eip-1559-v2',
ganacheOptions,
title: this.test.title,
},
async ({ driver }) => {
await driver.navigate();
await driver.fill('#password', 'correct horse battery staple');
await driver.press('#password', driver.Key.ENTER);
const transactionAmounts = await driver.findElements(
'.currency-display-component__text',
);
const transactionAmount = transactionAmounts[0];
assert.equal(await transactionAmount.getText(), '2.2');
// update estimates to high
await driver.clickElement('[data-testid="edit-gas-fee-button"]');
await driver.delay(regularDelayMs);
await driver.clickElement('[data-testid="edit-gas-fee-item-custom"]');
await driver.delay(regularDelayMs);
// enter max fee
const maxBaseFee = await driver.findElement(
'[data-testid="base-fee-input"]',
);
await maxBaseFee.clear();
await maxBaseFee.sendKeys('8');
await driver.delay(regularDelayMs);
// enter priority fee
const priorityFee = await driver.findElement(
'[data-testid="priority-fee-input"]',
);
await priorityFee.clear();
await priorityFee.sendKeys('8');
await driver.delay(regularDelayMs);
// save default values
await driver.clickElement('input[type="checkbox"]');
await driver.delay(regularDelayMs);
// edit gas limit
await driver.clickElement('[data-testid="advanced-gas-fee-edit"]');
await driver.delay(regularDelayMs);
const gasLimit = await driver.findElement(
'[data-testid="gas-limit-input"]',
);
await gasLimit.clear();
await gasLimit.sendKeys('100000');
await driver.delay(regularDelayMs);
// Submit gas fee changes
await driver.clickElement({ text: 'Save', tag: 'button' });
// has correct updated value on the confirm screen the transaction
const editedTransactionAmounts = await driver.findElements(
'.transaction-detail-item__row .transaction-detail-item__detail-values .currency-display-component__text:last-of-type',
);
const editedTransactionAmount = editedTransactionAmounts[0];
assert.equal(await editedTransactionAmount.getText(), '0.0008');
const editedTransactionFee = editedTransactionAmounts[1];
assert.equal(await editedTransactionFee.getText(), '2.2008');
// confirms the transaction
await driver.clickElement({ text: 'Confirm', tag: 'button' });
await driver.delay(regularDelayMs);
await driver.clickElement('[data-testid="home__activity-tab"]');
await driver.wait(async () => {
const confirmedTxes = await driver.findElements(
'.transaction-list__completed-transactions .transaction-list-item',
);
return confirmedTxes.length === 1;
}, 10000);
const txValues = await driver.findElements(
'.transaction-list-item__primary-currency',
);
assert.equal(txValues.length, 1);
assert.ok(/-2.2\s*ETH/u.test(await txValues[0].getText()));
},
);
});
it('should use dapp suggested estimates for transaction coming from dapp', async function () {
const ganacheOptions = {
hardfork: 'london',
accounts: [
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: convertToHexValue(25000000000000000000),
},
],
};
await withFixtures(
{
fixtures: 'eip-1559-v2-dapp',
ganacheOptions,
title: this.test.title,
dapp: true,
},
async ({ driver }) => {
await driver.navigate();
// login to extension
await driver.fill('#password', 'correct horse battery staple');
await driver.press('#password', driver.Key.ENTER);
// open dapp and connect
await connectDappWithExtensionPopup(driver);
await driver.clickElement({ text: 'Send', tag: 'button' });
// check transaction in extension popup
const windowHandles = await getWindowHandles(driver, 3);
await driver.switchToWindow(windowHandles.popup);
await driver.delay(largeDelayMs);
await driver.waitForSelector({ text: '🌐' });
await driver.waitForSelector({
text: 'Site suggested',
});
await driver.clickElement('[data-testid="edit-gas-fee-button"]');
await driver.delay(regularDelayMs);
await driver.clickElement(
'[data-testid="edit-gas-fee-item-dappSuggested"]',
);
await driver.delay(regularDelayMs);
const transactionAmounts = await driver.findElements(
'.currency-display-component__text',
);
const transactionAmount = transactionAmounts[0];
assert.equal(await transactionAmount.getText(), '3');
// has correct updated value on the confirm screen the transaction
const editedTransactionAmounts = await driver.findElements(
'.transaction-detail-item__row .transaction-detail-item__detail-values .currency-display-component__text:last-of-type',
);
const editedTransactionAmount = editedTransactionAmounts[0];
assert.equal(await editedTransactionAmount.getText(), '0.00042');
const editedTransactionFee = editedTransactionAmounts[1];
assert.equal(await editedTransactionFee.getText(), '3.00042');
// confirms the transaction
await driver.clickElement({ text: 'Confirm', tag: 'button' });
await driver.delay(regularDelayMs);
// transaction should correct values in activity tab
await driver.switchToWindow(windowHandles.extension);
await driver.clickElement('[data-testid="home__activity-tab"]');
await driver.wait(async () => {
const confirmedTxes = await driver.findElements(
'.transaction-list__completed-transactions .transaction-list-item',
);
return confirmedTxes.length === 1;
}, 10000);
const txValues = await driver.findElements(
'.transaction-list-item__primary-currency',
);
assert.equal(txValues.length, 1);
assert.ok(/-3\s*ETH/u.test(await txValues[0].getText()));
},
);
});
});
}

@ -1,5 +1,10 @@
const { strict: assert } = require('assert');
const { withFixtures, regularDelayMs, largeDelayMs } = require('../helpers');
const {
convertToHexValue,
withFixtures,
regularDelayMs,
largeDelayMs,
} = require('../helpers');
const enLocaleMessages = require('../../../app/_locales/en/messages.json');
describe('Metamask Import UI', function () {
@ -9,7 +14,7 @@ describe('Metamask Import UI', function () {
{
secretKey:
'0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};
@ -72,7 +77,7 @@ describe('Metamask Import UI', function () {
// Import Secret Recovery Phrase
await driver.fill(
'input[placeholder="Paste Secret Recovery Phrase from clipboard"]',
'input[placeholder="Enter your Secret Recovery Phrase"]',
testSeedPhrase,
);
@ -186,7 +191,7 @@ describe('Metamask Import UI', function () {
{
secretKey:
'0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};
@ -279,10 +284,10 @@ describe('Metamask Import UI', function () {
await driver.delay(regularDelayMs);
await driver.clickElement('.account-menu__icon');
const accountListItemsAgfterRemoval = await driver.findElements(
const accountListItemsAfterRemoval = await driver.findElements(
'.account-menu__account',
);
assert.equal(accountListItemsAgfterRemoval.length, 4);
assert.equal(accountListItemsAfterRemoval.length, 4);
},
);
});
@ -292,7 +297,7 @@ describe('Metamask Import UI', function () {
{
secretKey:
'0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,5 +1,5 @@
const { strict: assert } = require('assert');
const { withFixtures, tinyDelayMs } = require('../helpers');
const { convertToHexValue, withFixtures, tinyDelayMs } = require('../helpers');
const enLocaleMessages = require('../../../app/_locales/en/messages.json');
describe('Incremental Security', function () {
@ -8,12 +8,12 @@ describe('Incremental Security', function () {
{
secretKey:
'0x250F458997A364988956409A164BA4E16F0F99F916ACDD73ADCD3A1DE30CF8D1',
balance: 0,
balance: convertToHexValue(0),
},
{
secretKey:
'0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,5 +1,5 @@
const { strict: assert } = require('assert');
const { withFixtures } = require('../helpers');
const { convertToHexValue, withFixtures } = require('../helpers');
describe('Localization', function () {
it('can correctly display Philippine peso symbol and code', async function () {
@ -8,7 +8,7 @@ describe('Localization', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,5 +1,5 @@
const { strict: assert } = require('assert');
const { withFixtures } = require('../helpers');
const { convertToHexValue, withFixtures } = require('../helpers');
describe('Lock and unlock', function () {
const ganacheOptions = {
@ -7,7 +7,7 @@ describe('Lock and unlock', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -4,7 +4,7 @@ const {
getGlobalProperties,
testIntrinsic,
} = require('../../helpers/protect-intrinsics-helpers');
const { withFixtures } = require('../helpers');
const { convertToHexValue, withFixtures } = require('../helpers');
const { PAGES } = require('../webdriver/driver');
const isFirefox = process.env.SELENIUM_BROWSER === Browser.FIREFOX;
@ -56,7 +56,7 @@ describe('lockdown', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,5 +1,5 @@
const { strict: assert } = require('assert');
const { withFixtures, tinyDelayMs } = require('../helpers');
const { convertToHexValue, withFixtures, tinyDelayMs } = require('../helpers');
const enLocaleMessages = require('../../../app/_locales/en/messages.json');
describe('Metamask Responsive UI', function () {
@ -183,7 +183,7 @@ describe('Metamask Responsive UI', function () {
// balance renders
await driver.waitForSelector({
css: '[data-testid="eth-overview__primary-currency"]',
text: '100 ETH',
text: '1000 ETH',
});
},
);
@ -196,7 +196,7 @@ describe('Metamask Responsive UI', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,5 +1,5 @@
const { strict: assert } = require('assert');
const { withFixtures } = require('../helpers');
const { convertToHexValue, withFixtures } = require('../helpers');
describe('Navigate transactions', function () {
const ganacheOptions = {
@ -7,7 +7,7 @@ describe('Navigate transactions', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,5 +1,5 @@
const { strict: assert } = require('assert');
const { withFixtures } = require('../helpers');
const { convertToHexValue, withFixtures } = require('../helpers');
describe('Permissions', function () {
it('sets permissions and connect to Dapp', async function () {
@ -8,7 +8,7 @@ describe('Permissions', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,5 +1,5 @@
const { strict: assert } = require('assert');
const { withFixtures } = require('../helpers');
const { convertToHexValue, withFixtures } = require('../helpers');
describe('Personal sign', function () {
it('can initiate and confirm a personal sign', async function () {
@ -8,7 +8,7 @@ describe('Personal sign', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,6 +1,6 @@
const { strict: assert } = require('assert');
const { errorCodes } = require('eth-rpc-errors');
const { withFixtures } = require('../helpers');
const { convertToHexValue, withFixtures } = require('../helpers');
describe('MetaMask', function () {
const ganacheOptions = {
@ -8,7 +8,7 @@ describe('MetaMask', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,5 +1,6 @@
const { strict: assert } = require('assert');
const {
convertToHexValue,
withFixtures,
tinyDelayMs,
regularDelayMs,
@ -13,7 +14,7 @@ describe('Editing Confirm Transaction', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};
@ -90,4 +91,114 @@ describe('Editing Confirm Transaction', function () {
},
);
});
if (process.env.EIP_1559_V2 === '1') {
it('goes back from confirm page to edit eth value, baseFee, priorityFee and gas limit - 1559 V2', async function () {
const ganacheOptions = {
hardfork: 'london',
accounts: [
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: convertToHexValue(25000000000000000000),
},
],
};
await withFixtures(
{
fixtures: 'send-edit-v2',
ganacheOptions,
title: this.test.title,
},
async ({ driver }) => {
await driver.navigate();
await driver.fill('#password', 'correct horse battery staple');
await driver.press('#password', driver.Key.ENTER);
const transactionAmounts = await driver.findElements(
'.currency-display-component__text',
);
const transactionAmount = transactionAmounts[0];
assert.equal(await transactionAmount.getText(), '1');
const transactionFee = transactionAmounts[1];
assert.equal(await transactionFee.getText(), '0.0000375');
await driver.clickElement(
'.confirm-page-container-header__back-button',
);
await driver.fill('.unit-input__input', '2.2');
await driver.clickElement({ text: 'Next', tag: 'button' });
// open gas fee popover
await driver.clickElement({ text: 'Edit', tag: 'button' });
// show gas limit
await driver.clickElement('[data-testid="advanced-gas-fee-edit"]');
await driver.delay(largeDelayMs);
// enter max fee
const maxBaseFee = await driver.findElement(
'[data-testid="base-fee-input"]',
);
await maxBaseFee.clear();
await maxBaseFee.sendKeys('8');
await driver.delay(regularDelayMs);
// enter priority fee
const priorityFee = await driver.findElement(
'[data-testid="priority-fee-input"]',
);
await priorityFee.clear();
await priorityFee.sendKeys('8');
await driver.delay(regularDelayMs);
// edit gas limit
const gasLimit = await driver.findElement(
'[data-testid="gas-limit-input"]',
);
await gasLimit.clear();
await gasLimit.sendKeys('100000');
await driver.delay(regularDelayMs);
// save default values
await driver.clickElement('input[type="checkbox"]');
await driver.delay(regularDelayMs);
// Submit gas fee changes
await driver.clickElement({ text: 'Save', tag: 'button' });
// has correct updated value on the confirm screen the transaction
const editedTransactionAmounts = await driver.findElements(
'.transaction-detail-item__row .transaction-detail-item__detail-values .currency-display-component__text:last-of-type',
);
const editedTransactionAmount = editedTransactionAmounts[0];
assert.equal(await editedTransactionAmount.getText(), '0.0008');
const editedTransactionFee = editedTransactionAmounts[1];
assert.equal(await editedTransactionFee.getText(), '2.2008');
// confirms the transaction
await driver.clickElement({ text: 'Confirm', tag: 'button' });
await driver.delay(regularDelayMs);
await driver.clickElement('[data-testid="home__activity-tab"]');
await driver.wait(async () => {
const confirmedTxes = await driver.findElements(
'.transaction-list__completed-transactions .transaction-list-item',
);
return confirmedTxes.length === 1;
}, 10000);
const txValues = await driver.findElements(
'.transaction-list-item__primary-currency',
);
assert.equal(txValues.length, 1);
assert.ok(/-2.2\s*ETH/u.test(await txValues[0].getText()));
},
);
});
}
});

@ -1,5 +1,9 @@
const { strict: assert } = require('assert');
const { withFixtures, regularDelayMs } = require('../helpers');
const {
convertToHexValue,
withFixtures,
regularDelayMs,
} = require('../helpers');
describe('Send ETH from inside MetaMask using default gas', function () {
const ganacheOptions = {
@ -7,7 +11,7 @@ describe('Send ETH from inside MetaMask using default gas', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};
@ -93,7 +97,7 @@ describe('Send ETH from inside MetaMask using advanced gas modal', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};
@ -162,7 +166,7 @@ describe('Send ETH from dapp using advanced gas controls', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,5 +1,5 @@
const { strict: assert } = require('assert');
const { withFixtures } = require('../helpers');
const { convertToHexValue, withFixtures } = require('../helpers');
describe('Signature Request', function () {
it('can initiate and confirm a Signature Request', async function () {
@ -8,7 +8,7 @@ describe('Signature Request', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,4 +1,4 @@
const { withFixtures } = require('../helpers');
const { convertToHexValue, withFixtures } = require('../helpers');
describe('Simple send', function () {
it('can send a simple transaction from one account to another', async function () {
@ -7,7 +7,7 @@ describe('Simple send', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,5 +1,5 @@
const { strict: assert } = require('assert');
const { withFixtures, largeDelayMs } = require('../helpers');
const { convertToHexValue, withFixtures, largeDelayMs } = require('../helpers');
const ThreeboxMockServer = require('../mock-3box/threebox-mock-server');
describe('Threebox', function () {
@ -8,7 +8,7 @@ describe('Threebox', function () {
{
secretKey:
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
balance: 25000000000000000000,
balance: convertToHexValue(25000000000000000000),
},
],
};

@ -1,6 +1,6 @@
import { JsonRpcEngine, createScaffoldMiddleware } from 'json-rpc-engine';
import { providerAsMiddleware } from 'eth-json-rpc-middleware';
import GanacheCore from 'ganache-core';
import Ganache from 'ganache';
export function getTestSeed() {
return 'people carpet cluster attract ankle motor ozone mass dove original primary mask';
@ -48,11 +48,11 @@ export function createTestProviderTools(opts = {}) {
// handle block tracker methods
engine.push(
providerAsMiddleware(
GanacheCore.provider({
Ganache.provider({
mnemonic: getTestSeed(),
network_id: opts.networkId,
_chainId: opts.chainId,
_chainIdRpc: opts.chainId,
chain: { chainId: opts.chainId },
hardfork: 'muirGlacier',
}),
),
);

@ -16,7 +16,6 @@ import {
SUPPORT_REQUEST_LINK,
///: END:ONLY_INCLUDE_IN
} from '../../../helpers/constants/common';
import { KEYRING_TYPES } from '../../../../shared/constants/hardware-wallets';
import {
SETTINGS_ROUTE,
NEW_ACCOUNT_ROUTE,
@ -27,6 +26,7 @@ import {
import TextField from '../../ui/text-field';
import SearchIcon from '../../ui/search-icon';
import Button from '../../ui/button';
import KeyRingLabel from './keyring-label';
export function AccountMenuItem(props) {
const { icon, children, text, subText, className, onClick } = props;
@ -214,7 +214,7 @@ export default class AccountMenu extends Component {
type={PRIMARY}
/>
</div>
{this.renderKeyringType(keyring)}
<KeyRingLabel keyring={keyring} />
{iconAndNameForOpenSubject ? (
<div className="account-menu__icon-list">
<SiteIcon
@ -229,34 +229,6 @@ export default class AccountMenu extends Component {
});
}
renderKeyringType(keyring) {
const { t } = this.context;
// Sometimes keyrings aren't loaded yet
if (!keyring) {
return null;
}
const { type } = keyring;
let label;
switch (type) {
case KEYRING_TYPES.TREZOR:
case KEYRING_TYPES.LEDGER:
case KEYRING_TYPES.LATTICE:
case KEYRING_TYPES.QR:
label = t('hardware');
break;
case 'Simple Key Pair':
label = t('imported');
break;
default:
return null;
}
return <div className="keyring-label allcaps">{label}</div>;
}
resetSearchQuery() {
this.setSearchQuery('');
}

@ -96,7 +96,7 @@ describe('Account Menu', () => {
it('render imported account label', () => {
const importedAccount = wrapper.find('.keyring-label.allcaps');
expect(importedAccount.text()).toStrictEqual('imported');
expect(importedAccount.text()).toStrictEqual('[imported]');
});
});

@ -10,7 +10,9 @@
color: var(--white);
@media screen and (max-width: $break-small) {
right: calc(((100vw - 100%) / 2) + 8px);
position: initial;
margin-top: -10px;
margin-left: calc(((100vw - 100%) / 2) + 8px);
}
@media screen and (min-width: $break-large) {

@ -0,0 +1,48 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useI18nContext } from '../../../hooks/useI18nContext';
import {
KEYRING_NAMES,
KEYRING_TYPES,
} from '../../../../shared/constants/hardware-wallets';
export default function KeyRingLabel({ keyring }) {
const t = useI18nContext();
let label = null;
// Keyring value might take a while to get a value
if (!keyring) {
return null;
}
const { type } = keyring;
switch (type) {
case KEYRING_TYPES.QR:
label = KEYRING_NAMES.QR;
break;
case 'Simple Key Pair':
label = t('imported');
break;
case KEYRING_TYPES.TREZOR:
label = KEYRING_NAMES.TREZOR;
break;
case KEYRING_TYPES.LEDGER:
label = KEYRING_NAMES.LEDGER;
break;
case KEYRING_TYPES.LATTICE:
label = KEYRING_NAMES.LATTICE;
break;
default:
return null;
}
return (
<>{label ? <div className="keyring-label allcaps">{label}</div> : null}</>
);
}
KeyRingLabel.propTypes = {
keyring: PropTypes.object,
};

@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import Box from '../../../ui/box';
@ -19,7 +19,6 @@ import { useI18nContext } from '../../../../hooks/useI18nContext';
const AdvancedGasFeeDefaults = () => {
const t = useI18nContext();
const dispatch = useDispatch();
const {
hasErrors,
maxBaseFee,
@ -27,25 +26,34 @@ const AdvancedGasFeeDefaults = () => {
} = useAdvancedGasFeePopoverContext();
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
const updateDefaultSettings = (value) => {
if (value) {
const [isDefaultSettingsSelected, setDefaultSettingsSelected] = useState(
Boolean(advancedGasFeeValues) &&
advancedGasFeeValues.maxBaseFee === maxBaseFee &&
advancedGasFeeValues.priorityFee === maxPriorityFeePerGas,
);
useEffect(() => {
setDefaultSettingsSelected(
Boolean(advancedGasFeeValues) &&
advancedGasFeeValues.maxBaseFee === maxBaseFee &&
advancedGasFeeValues.priorityFee === maxPriorityFeePerGas,
);
}, [advancedGasFeeValues, maxBaseFee, maxPriorityFeePerGas]);
const handleUpdateDefaultSettings = () => {
if (isDefaultSettingsSelected) {
dispatch(setAdvancedGasFee(null));
setDefaultSettingsSelected(false);
} else {
dispatch(
setAdvancedGasFee({
maxBaseFee,
priorityFee: maxPriorityFeePerGas,
}),
);
} else {
dispatch(setAdvancedGasFee(null));
setDefaultSettingsSelected(true);
}
};
const isDefaultSettingsSelected =
Boolean(advancedGasFeeValues) &&
advancedGasFeeValues.maxBaseFee === maxBaseFee &&
advancedGasFeeValues.priorityFee === maxPriorityFeePerGas;
const handleUpdateDefaultSettings = () =>
updateDefaultSettings(!isDefaultSettingsSelected);
return (
<Box
@ -54,19 +62,21 @@ const AdvancedGasFeeDefaults = () => {
marginRight={4}
className="advanced-gas-fee-defaults"
>
<CheckBox
checked={isDefaultSettingsSelected}
className="advanced-gas-fee-defaults__checkbox"
onClick={handleUpdateDefaultSettings}
disabled={hasErrors}
/>
<Typography variant={TYPOGRAPHY.H7} color={COLORS.UI4} margin={0}>
{!isDefaultSettingsSelected && Boolean(advancedGasFeeValues)
? t('advancedGasFeeDefaultOptIn', [
<strong key="default-value-change">{t('newValues')}</strong>,
])
: t('advancedGasFeeDefaultOptOut')}
</Typography>
<label className="advanced-gas-fee-defaults__label">
<CheckBox
checked={isDefaultSettingsSelected}
className="advanced-gas-fee-defaults__checkbox"
onClick={handleUpdateDefaultSettings}
disabled={hasErrors}
/>
<Typography variant={TYPOGRAPHY.H7} color={COLORS.UI4} margin={0}>
{!isDefaultSettingsSelected && Boolean(advancedGasFeeValues)
? t('advancedGasFeeDefaultOptIn', [
<strong key="default-value-change">{t('newValues')}</strong>,
])
: t('advancedGasFeeDefaultOptOut')}
</Typography>
</label>
</Box>
);
};

@ -5,6 +5,7 @@ import { GAS_ESTIMATE_TYPES } from '../../../../../shared/constants/gas';
import { renderWithProvider } from '../../../../../test/lib/render-helpers';
import mockEstimates from '../../../../../test/data/mock-estimates.json';
import mockState from '../../../../../test/data/mock-state.json';
import * as Actions from '../../../../store/actions';
import { AdvancedGasFeePopoverContextProvider } from '../context';
import { GasFeeContextProvider } from '../../../../contexts/gasFee';
@ -20,6 +21,7 @@ jest.mock('../../../../store/actions', () => ({
.mockImplementation(() => Promise.resolve()),
addPollingTokenToAppState: jest.fn(),
removePollingTokenFromAppState: jest.fn(),
setAdvancedGasFee: jest.fn(),
}));
const render = (defaultGasParams) => {
@ -125,4 +127,21 @@ describe('AdvancedGasFeeDefaults', () => {
screen.queryByText('Save these as my default for "Advanced"'),
).toBeInTheDocument();
});
it('should call action setAdvancedGasFee when checkbox or label text is clicked', () => {
render({
advancedGasFee: { maxBaseFee: 50, priorityFee: 2 },
});
const mock = jest
.spyOn(Actions, 'setAdvancedGasFee')
.mockReturnValue({ type: 'test' });
const checkboxLabel = screen.queryByText(
'Always use these values and advanced setting as default.',
);
fireEvent.click(checkboxLabel);
expect(mock).toHaveBeenCalledTimes(1);
const checkbox = document.querySelector('input[type=checkbox]');
fireEvent.click(checkbox);
expect(mock).toHaveBeenCalledTimes(2);
});
});

@ -3,4 +3,8 @@
font-size: $font-size-h4;
margin: 0 8px 0 8px;
}
&__label {
display: flex;
}
}

@ -45,6 +45,7 @@ const AdvancedGasFeeGasLimit = () => {
if (isEditing) {
return (
<FormField
dataTestId="gas-limit-input"
error={
gasLimitError
? t(gasLimitError, [minimumGasLimitDec - 1, MAX_GAS_LIMIT_DEC])
@ -70,6 +71,7 @@ const AdvancedGasFeeGasLimit = () => {
</strong>
<span>{gasLimit}</span>
<Button
data-testid="advanced-gas-fee-edit"
className="advanced-gas-fee-gas-limit__edit-link"
onClick={() => setEditing(true)}
type="link"

@ -4,7 +4,11 @@ import { useSelector } from 'react-redux';
import { HIGH_FEE_WARNING_MULTIPLIER } from '../../../../../pages/send/send.constants';
import { PRIORITY_LEVELS } from '../../../../../../shared/constants/gas';
import { SECONDARY } from '../../../../../helpers/constants/common';
import { bnGreaterThan, bnLessThan } from '../../../../../helpers/utils/util';
import {
bnGreaterThan,
bnLessThan,
roundToDecimalPlacesRemovingExtraZeroes,
} from '../../../../../helpers/utils/util';
import { decGWEIToHexWEI } from '../../../../../helpers/utils/conversions.util';
import { getAdvancedGasFeeValues } from '../../../../../selectors';
import { useGasFeeContext } from '../../../../../contexts/gasFee';
@ -16,10 +20,7 @@ import FormField from '../../../../ui/form-field';
import { useAdvancedGasFeePopoverContext } from '../../context';
import AdvancedGasFeeInputSubtext from '../../advanced-gas-fee-input-subtext';
import {
roundToDecimalPlacesRemovingExtraZeroes,
renderFeeRange,
} from '../utils';
import { renderFeeRange } from '../utils';
const validateBaseFee = (value, gasFeeEstimates, maxPriorityFeePerGas) => {
if (bnGreaterThan(maxPriorityFeePerGas, value)) {
@ -60,10 +61,7 @@ const BaseFeeInput = () => {
baseFeeTrend,
} = gasFeeEstimates;
const [baseFeeError, setBaseFeeError] = useState();
const {
currency,
numberOfDecimals: numberOfDecimalsFiat,
} = useUserPreferencedCurrency(SECONDARY);
const { currency, numberOfDecimals } = useUserPreferencedCurrency(SECONDARY);
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
@ -80,7 +78,7 @@ const BaseFeeInput = () => {
const [, { value: baseFeeInFiat }] = useCurrencyDisplay(
decGWEIToHexWEI(baseFee),
{ currency, numberOfDecimalsFiat },
{ currency, numberOfDecimals },
);
const updateBaseFee = useCallback(
@ -114,6 +112,7 @@ const BaseFeeInput = () => {
return (
<Box className="base-fee-input" margin={[0, 2]}>
<FormField
dataTestId="base-fee-input"
error={baseFeeError ? t(baseFeeError) : ''}
onChange={updateBaseFee}
titleText={t('maxBaseFee')}

@ -100,6 +100,7 @@ const PriorityFeeInput = () => {
return (
<Box margin={[0, 2]}>
<FormField
dataTestId="priority-fee-input"
error={priorityFeeError ? t(priorityFeeError) : ''}
onChange={updatePriorityFee}
titleText={t('priorityFeeProperCase')}

@ -1,17 +1,6 @@
import { uniq } from 'lodash';
import { toBigNumber } from '../../../../../shared/modules/conversion.utils';
export function roundToDecimalPlacesRemovingExtraZeroes(
numberish,
numberOfDecimalPlaces,
) {
if (numberish) {
return toBigNumber.dec(
toBigNumber.dec(numberish).toFixed(numberOfDecimalPlaces),
);
}
return null;
}
import { roundToDecimalPlacesRemovingExtraZeroes } from '../../../../helpers/utils/util';
export const renderFeeRange = (feeRange) => {
if (feeRange) {

@ -13,11 +13,7 @@ import AdvancedGasFeeDefaults from './advanced-gas-fee-defaults';
const AdvancedGasFeePopover = () => {
const t = useI18nContext();
const {
closeAllModals,
closeModal,
currentModal,
} = useTransactionModalContext();
const { closeAllModals, currentModal } = useTransactionModalContext();
if (currentModal !== 'advancedGasFee') {
return null;
@ -28,7 +24,6 @@ const AdvancedGasFeePopover = () => {
<Popover
className="advanced-gas-fee-popover"
title={t('advancedGasFeeModalTitle')}
onBack={() => closeModal('advancedGasFee')}
onClose={closeAllModals}
footer={<AdvancedGasFeeSaveButton />}
>

@ -9,6 +9,7 @@
@import 'asset-list-item/asset-list-item';
@import 'cancel-speedup-popover/index';
@import 'confirm-page-container/index';
@import 'confirm-page-container/enableEIP1559V2-notice';
@import 'collectibles-items/index';
@import 'collectibles-tab/index';
@import 'collectible-details/index';
@ -73,3 +74,4 @@
@import 'advanced-gas-fee-popover/advanced-gas-fee-inputs/base-fee-input/index';
@import 'advanced-gas-fee-popover/advanced-gas-fee-input-subtext/index';
@import 'advanced-gas-fee-popover/advanced-gas-fee-defaults/index';
@import 'currency-input/index';

@ -13,6 +13,7 @@ import { useMetricEvent } from '../../../hooks/useMetricEvent';
import { ASSET_TYPES, updateSendAsset } from '../../../ducks/send';
import { SEND_ROUTE } from '../../../helpers/constants/routes';
import { SEVERITIES } from '../../../helpers/constants/design-system';
import { INVALID_ASSET_TYPE } from '../../../helpers/constants/error-keys';
const AssetListItem = ({
className,
@ -65,21 +66,26 @@ const AssetListItem = ({
<Button
type="link"
className="asset-list-item__send-token-button"
onClick={(e) => {
onClick={async (e) => {
e.stopPropagation();
sendTokenEvent();
dispatch(
updateSendAsset({
type: ASSET_TYPES.TOKEN,
details: {
address: tokenAddress,
decimals: tokenDecimals,
symbol: tokenSymbol,
},
}),
).then(() => {
try {
await dispatch(
updateSendAsset({
type: ASSET_TYPES.TOKEN,
details: {
address: tokenAddress,
decimals: tokenDecimals,
symbol: tokenSymbol,
},
}),
);
history.push(SEND_ROUTE);
});
} catch (err) {
if (!err.message.includes(INVALID_ASSET_TYPE)) {
throw err;
}
}
}}
>
{t('sendSpecifiedTokens', [tokenSymbol])}

@ -54,7 +54,7 @@ const CancelSpeedupPopover = () => {
const gasUsedLessThanMedium =
gasFeeEstimates &&
gasEstimateGreaterThanGasUsedPlusTenPercent(
transaction,
transaction.txParams,
gasFeeEstimates,
PRIORITY_LEVELS.MEDIUM,
);

@ -30,6 +30,7 @@ import {
getSelectedIdentity,
} from '../../../selectors';
import AssetNavigation from '../../../pages/asset/components/asset-navigation';
import Copy from '../../ui/icon/copy-icon.component';
import { getCollectibleContracts } from '../../../ducks/metamask/metamask';
import { DEFAULT_ROUTE, SEND_ROUTE } from '../../../helpers/constants/routes';
import {
@ -52,10 +53,12 @@ import { ASSET_TYPES, updateSendAsset } from '../../../ducks/send';
import InfoTooltip from '../../ui/info-tooltip';
import { ERC721 } from '../../../helpers/constants/common';
import { usePrevious } from '../../../hooks/usePrevious';
import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
export default function CollectibleDetails({ collectible }) {
const {
image,
imageOriginal,
name,
description,
address,
@ -70,6 +73,7 @@ export default function CollectibleDetails({ collectible }) {
const ipfsGateway = useSelector(getIpfsGateway);
const collectibleContracts = useSelector(getCollectibleContracts);
const currentNetwork = useSelector(getCurrentChainId);
const [copied, handleCopy] = useCopyToClipboard();
const collectibleContractName = collectibleContracts.find(
({ address: contractAddress }) =>
@ -78,7 +82,10 @@ export default function CollectibleDetails({ collectible }) {
const selectedAccountName = useSelector(
(state) => getSelectedIdentity(state).name,
);
const collectibleImageURL = getAssetImageURL(image, ipfsGateway);
const collectibleImageURL = getAssetImageURL(
imageOriginal ?? image,
ipfsGateway,
);
const onRemove = () => {
dispatch(removeAndIgnoreCollectible(address, tokenId));
@ -130,6 +137,7 @@ export default function CollectibleDetails({ collectible }) {
<Box
display={DISPLAY.FLEX}
width={inPopUp ? BLOCK_SIZES.FULL : BLOCK_SIZES.HALF}
margin={inPopUp ? [4, 0] : null}
>
<Button
type="primary"
@ -170,10 +178,7 @@ export default function CollectibleDetails({ collectible }) {
justifyContent={JUSTIFY_CONTENT.CENTER}
className="collectible-details__card"
>
<img
className="collectible-details__image"
src={collectibleImageURL}
/>
<img className="collectible-details__image" src={image} />
</Card>
<Box
flexDirection={FLEX_DIRECTION.COLUMN}
@ -185,37 +190,39 @@ export default function CollectibleDetails({ collectible }) {
color={COLORS.BLACK}
variant={TYPOGRAPHY.H4}
fontWeight={FONT_WEIGHT.BOLD}
boxProps={{ margin: 0, marginBottom: 4 }}
boxProps={{ margin: 0, marginBottom: 2 }}
>
{name}
</Typography>
<Typography
color={COLORS.UI3}
variant={TYPOGRAPHY.H5}
boxProps={{ margin: 0 }}
boxProps={{ margin: 0, marginBottom: 4 }}
overflowWrap={OVERFLOW_WRAP.BREAK_WORD}
>
#{tokenId}
</Typography>
</div>
<div>
<Typography
color={COLORS.BLACK}
variant={TYPOGRAPHY.H6}
fontWeight={FONT_WEIGHT.BOLD}
className="collectible-details__description"
boxProps={{ margin: 0, marginBottom: 2 }}
>
{t('description')}
</Typography>
<Typography
color={COLORS.UI4}
variant={TYPOGRAPHY.H6}
boxProps={{ margin: 0, marginBottom: 4 }}
>
{description}
</Typography>
</div>
{description ? (
<div>
<Typography
color={COLORS.BLACK}
variant={TYPOGRAPHY.H6}
fontWeight={FONT_WEIGHT.BOLD}
className="collectible-details__description"
boxProps={{ margin: 0, marginBottom: 2 }}
>
{t('description')}
</Typography>
<Typography
color={COLORS.UI4}
variant={TYPOGRAPHY.H6}
boxProps={{ margin: 0, marginBottom: 4 }}
>
{description}
</Typography>
</div>
) : null}
{inPopUp ? null : renderSendButton()}
</Box>
</div>
@ -241,15 +248,15 @@ export default function CollectibleDetails({ collectible }) {
margin: 0,
marginBottom: 4,
}}
overflowWrap={OVERFLOW_WRAP.BREAK_WORD}
className="collectible-details__image-link"
>
<a
target="_blank"
href={collectibleImageURL}
rel="noopener noreferrer"
className="collectible-details__image-link"
href={collectibleImageURL}
title={collectibleImageURL}
>
{image}
{collectibleImageURL}
</a>
</Typography>
</Box>
@ -267,32 +274,49 @@ export default function CollectibleDetails({ collectible }) {
>
{t('contractAddress')}
</Typography>
<Typography
color={COLORS.UI3}
variant={TYPOGRAPHY.H6}
overflowWrap={OVERFLOW_WRAP.BREAK_WORD}
boxProps={{
margin: 0,
marginBottom: 4,
}}
<Box
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.ROW}
className="collectible-details__contract-wrapper"
>
<a
target="_blank"
<Typography
color={COLORS.PRIMARY1}
variant={TYPOGRAPHY.H6}
overflowWrap={OVERFLOW_WRAP.BREAK_WORD}
boxProps={{
margin: 0,
marginBottom: 4,
}}
className="collectible-details__contract-link"
href={getTokenTrackerLink(
address,
currentNetwork,
null,
null,
rpcPrefs,
)}
rel="noopener noreferrer"
>
{getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
? shortenAddress(address)
: address}
</a>
</Typography>
<a
target="_blank"
rel="noopener noreferrer"
href={getTokenTrackerLink(
address,
currentNetwork,
null,
null,
rpcPrefs,
)}
title={address}
>
{inPopUp ? shortenAddress(address) : address}
</a>
</Typography>
<button
className="collectible-details__contract-copy-button"
onClick={() => {
handleCopy(address);
}}
>
{copied ? (
t('copiedExclamation')
) : (
<Copy size={15} color="#6a737d" />
)}
</button>
</Box>
</Box>
{inPopUp ? renderSendButton() : null}
</Box>
@ -312,6 +336,7 @@ CollectibleDetails.propTypes = {
standard: PropTypes.string,
imageThumbnail: PropTypes.string,
imagePreview: PropTypes.string,
imageOriginal: PropTypes.string,
creator: PropTypes.shape({
address: PropTypes.string,
config: PropTypes.string,

@ -53,16 +53,38 @@ $spacer-break-small: 16px;
overflow-wrap: break-word;
}
&__contract-link,
&__image-link {
color: var(--primary-1);
&__contract-wrapper {
max-width: calc(100% - #{$link-title-width});
}
&__contract-copy-button {
@include H6;
width: 80px;
display: flex;
align-items: flex-start;
justify-content: center;
background-color: transparent;
cursor: pointer;
color: var(--ui-4);
border: 0;
&:active {
transform: scale(0.97);
}
}
&__contract-link {
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
}
&:hover {
color: var(--primary-3);
}
&__image-link {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 332px;
}
&__link-title {

@ -20,7 +20,7 @@ export default function CollectiblesDetectionNotice() {
const history = useHistory();
return (
<Box marginBottom={8} className="collectibles-detection-notice">
<Box marginBottom={4} className="collectibles-detection-notice">
<Dialog type="message" className="collectibles-detection-notice__message">
<button
onClick={() => setCollectiblesDetectionNoticeDismissed()}

@ -22,7 +22,7 @@
a.collectibles-detection-notice__message__link {
@include H6;
width: 60%;
width: 100%;
padding: 0;
justify-content: flex-start;
font-weight: bold;

@ -1,9 +1,10 @@
import React, { useState } from 'react';
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import Box from '../../ui/box';
import Typography from '../../ui/typography/typography';
import Card from '../../ui/card';
import {
COLORS,
TYPOGRAPHY,
@ -19,6 +20,9 @@ import { getEnvironmentType } from '../../../../app/scripts/lib/util';
import { getIpfsGateway } from '../../../selectors';
import { ASSET_ROUTE } from '../../../helpers/constants/routes';
import { getAssetImageURL } from '../../../helpers/utils/util';
import { updateCollectibleDropDownState } from '../../../store/actions';
import { usePrevious } from '../../../hooks/usePrevious';
import { getCollectiblesDropdownState } from '../../../ducks/metamask/metamask';
const width =
getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
@ -31,20 +35,30 @@ export default function CollectiblesItems({
collections = {},
previouslyOwnedCollection = {},
}) {
const dispatch = useDispatch();
const collectionsKeys = Object.keys(collections);
const collectiblesDropdownState = useSelector(getCollectiblesDropdownState);
const previousCollectionKeys = usePrevious(collectionsKeys);
// if there is only one collection present set it to open when component mounts
const [dropdownState, setDropdownState] = useState(() => {
return collectionsKeys.length === 1
? {
[PREVIOUSLY_OWNED_KEY]: false,
[collectionsKeys[0]]: true,
}
: { [PREVIOUSLY_OWNED_KEY]: false };
});
useEffect(() => {
if (
!Object.keys(collectiblesDropdownState).length &&
previousCollectionKeys !== collectionsKeys
) {
const initState = {};
collectionsKeys.forEach((key) => {
initState[key] = true;
});
dispatch(updateCollectibleDropDownState(initState));
}
}, [
collectionsKeys,
previousCollectionKeys,
collectiblesDropdownState,
dispatch,
]);
const ipfsGateway = useSelector(getIpfsGateway);
const history = useHistory();
const renderCollectionImage = (
@ -81,46 +95,50 @@ export default function CollectiblesItems({
return null;
}
const isExpanded = dropdownState[key];
const isExpanded = collectiblesDropdownState[key];
return (
<div
className="collectibles-items__collection"
key={`collection-${key}`}
onClick={() => {
setDropdownState((_dropdownState) => ({
..._dropdownState,
[key]: !isExpanded,
}));
}}
>
<Box
marginBottom={2}
display={DISPLAY.FLEX}
alignItems={ALIGN_ITEMS.CENTER}
justifyContent={JUSTIFY_CONTENT.SPACE_BETWEEN}
className="collectibles-items__collection-accordion-title"
<div className="collectibles-items__collection" key={`collection-${key}`}>
<button
onClick={() => {
dispatch(
updateCollectibleDropDownState({
...collectiblesDropdownState,
[key]: !isExpanded,
}),
);
}}
className="collectibles-items__collection-wrapper"
>
<Box
marginBottom={2}
display={DISPLAY.FLEX}
alignItems={ALIGN_ITEMS.CENTER}
className="collectibles-items__collection-header"
justifyContent={JUSTIFY_CONTENT.SPACE_BETWEEN}
className="collectibles-items__collection-accordion-title"
>
{renderCollectionImage(
isPreviouslyOwnedCollection,
collectionImage,
collectionName,
)}
<Typography
color={COLORS.BLACK}
variant={TYPOGRAPHY.H5}
margin={[0, 0, 0, 2]}
<Box
alignItems={ALIGN_ITEMS.CENTER}
className="collectibles-items__collection-header"
>
{`${collectionName} (${collectibles.length})`}
</Typography>
</Box>
<Box alignItems={ALIGN_ITEMS.FLEX_END}>
<i className={`fa fa-chevron-${isExpanded ? 'down' : 'right'}`} />
{renderCollectionImage(
isPreviouslyOwnedCollection,
collectionImage,
collectionName,
)}
<Typography
color={COLORS.BLACK}
variant={TYPOGRAPHY.H5}
margin={[0, 0, 0, 2]}
>
{`${collectionName} (${collectibles.length})`}
</Typography>
</Box>
<Box alignItems={ALIGN_ITEMS.FLEX_END}>
<i className={`fa fa-chevron-${isExpanded ? 'down' : 'right'}`} />
</Box>
</Box>
</Box>
</button>
{isExpanded ? (
<Box display={DISPLAY.FLEX} flexWrap={FLEX_WRAP.WRAP} gap={4}>
{collectibles.map((collectible, i) => {
@ -132,20 +150,22 @@ export default function CollectiblesItems({
key={`collectible-${i}`}
className="collectibles-items__collection-item-wrapper"
>
<div
className="collectibles-items__collection-item"
style={{
backgroundColor,
}}
>
<img
onClick={() =>
history.push(`${ASSET_ROUTE}/${address}/${tokenId}`)
}
className="collectibles-items__collection-item-image"
src={collectibleImage}
/>
</div>
<Card padding={0} justifyContent={JUSTIFY_CONTENT.CENTER}>
<div
className="collectibles-items__collection-item"
style={{
backgroundColor,
}}
>
<img
onClick={() =>
history.push(`${ASSET_ROUTE}/${address}/${tokenId}`)
}
className="collectibles-items__collection-item-image"
src={collectibleImage}
/>
</div>
</Card>
</Box>
);
})}

@ -6,6 +6,12 @@
cursor: pointer;
}
&-wrapper {
background-color: transparent;
border: 0;
width: 100%;
}
&-image {
width: 32px;
height: 32px;

@ -117,6 +117,7 @@ export default function CollectiblesTab({ onAddNFT }) {
marginBottom={12}
justifyContent={JUSTIFY_CONTENT.CENTER}
flexDirection={FLEX_DIRECTION.COLUMN}
className="collectibles-tab__link"
>
<Typography
color={COLORS.UI3}
@ -131,7 +132,6 @@ export default function CollectiblesTab({ onAddNFT }) {
target="_blank"
rel="noopener noreferrer"
href="https://metamask.zendesk.com/hc/en-us/articles/360058238591-NFT-tokens-in-MetaMask-wallet"
style={{ padding: 0, fontSize: '1rem' }}
>
{t('learnMoreUpperCase')}
</Button>
@ -154,27 +154,31 @@ export default function CollectiblesTab({ onAddNFT }) {
alignItems={ALIGN_ITEMS.CENTER}
justifyContent={JUSTIFY_CONTENT.CENTER}
>
<Box
className="collectibles-tab__link"
justifyContent={JUSTIFY_CONTENT.FLEX_END}
>
{isMainnet && !useCollectibleDetection ? (
<Button type="link" onClick={onEnableAutoDetect}>
{t('enableAutoDetect')}
</Button>
) : (
<Button type="link" onClick={onRefresh}>
{t('refreshList')}
</Button>
)}
</Box>
<Typography
color={COLORS.UI3}
variant={TYPOGRAPHY.H4}
align={TEXT_ALIGN.CENTER}
>
{t('or')}
</Typography>
{!isMainnet && Object.keys(collections).length < 1 ? null : (
<>
<Box
className="collectibles-tab__link"
justifyContent={JUSTIFY_CONTENT.FLEX_END}
>
{isMainnet && !useCollectibleDetection ? (
<Button type="link" onClick={onEnableAutoDetect}>
{t('enableAutoDetect')}
</Button>
) : (
<Button type="link" onClick={onRefresh}>
{t('refreshList')}
</Button>
)}
</Box>
<Typography
color={COLORS.UI3}
variant={TYPOGRAPHY.H6}
align={TEXT_ALIGN.CENTER}
>
{t('or')}
</Typography>
</>
)}
<Box
justifyContent={JUSTIFY_CONTENT.FLEX_START}
className="collectibles-tab__link"

@ -136,6 +136,11 @@ const COLLECTIBLES_CONTRACTS = [
},
];
const collectiblesDropdownState = {
0x495f947276749ce646f68ac8c248420045cb7b5e: true,
0xdc7382eb0bc9c352a4cba23c909bda01e0206414: true,
};
const ACCOUNT_1 = '0x123';
const ACCOUNT_2 = '0x456';
@ -164,6 +169,7 @@ const render = ({
selectedAddress,
collectiblesDetectionNoticeDismissed,
useCollectibleDetection,
collectiblesDropdownState,
},
});
return renderWithProvider(<CollectiblesTab onAddNFT={onAddNFT} />, store);

@ -2,7 +2,7 @@
&__link {
a {
padding: 4px;
font-size: 1rem;
font-size: 0.875rem;
}
}
}

@ -11,6 +11,7 @@ describe('Confirm Page Container Content', () => {
provider: {
type: 'test',
},
eip1559V2Enabled: false,
},
};
@ -41,8 +42,6 @@ describe('Confirm Page Container Content', () => {
});
it('render ConfirmPageContainer component with simulation error', async () => {
process.env.EIP_1559_V2 = false;
const { queryByText, getByText } = renderWithProvider(
<ConfirmPageContainerContent {...props} />,
store,

@ -84,7 +84,7 @@ ConfirmPageContainerSummary.propTypes = {
identiconAddress: PropTypes.string,
nonce: PropTypes.string,
origin: PropTypes.string.isRequired,
hideTitle: PropTypes.boolean,
hideTitle: PropTypes.bool,
};
export default ConfirmPageContainerSummary;

@ -16,6 +16,7 @@ import AdvancedGasFeePopover from '../advanced-gas-fee-popover';
import EditGasFeePopover from '../edit-gas-fee-popover/edit-gas-fee-popover';
import EditGasPopover from '../edit-gas-popover';
import EnableEIP1559V2Notice from './enableEIP1559V2-notice';
import {
ConfirmPageContainerHeader,
ConfirmPageContainerContent,
@ -204,6 +205,7 @@ export default class ConfirmPageContainer extends Component {
</>
)}
</div>
<EnableEIP1559V2Notice isFirstAlert={!showAddToAddressDialog} />
{contentComponent || (
<ConfirmPageContainerContent
action={action}

@ -0,0 +1,94 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useHistory } from 'react-router-dom';
import { useSelector } from 'react-redux';
import Box from '../../../ui/box';
import Dialog from '../../../ui/dialog';
import Typography from '../../../ui/typography/typography';
import {
COLORS,
TYPOGRAPHY,
TEXT_ALIGN,
FONT_WEIGHT,
DISPLAY,
} from '../../../../helpers/constants/design-system';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import Button from '../../../ui/button';
import { EXPERIMENTAL_ROUTE } from '../../../../helpers/constants/routes';
import { setEnableEIP1559V2NoticeDismissed } from '../../../../store/actions';
import { getEnableEIP1559V2NoticeDismissed } from '../../../../ducks/metamask/metamask';
import { getEIP1559V2Enabled } from '../../../../selectors';
const EIP_1559_V2_ENABLED =
// This is a string in unit tests but is a boolean in the browser
process.env.EIP_1559_V2 === true || process.env.EIP_1559_V2 === 'true';
export default function EnableEIP1559V2Notice({ isFirstAlert }) {
const t = useI18nContext();
const history = useHistory();
const enableEIP1559V2NoticeDismissed = useSelector(
getEnableEIP1559V2NoticeDismissed,
);
const eip1559V2Enabled = useSelector(getEIP1559V2Enabled);
if (
!EIP_1559_V2_ENABLED ||
eip1559V2Enabled ||
enableEIP1559V2NoticeDismissed
) {
return null;
}
return (
<Box
margin={[0, 4, 4, 4]}
marginTop={isFirstAlert ? 4 : 0}
className="enableEIP1559V2-notice"
>
<Dialog type="message" className="enableEIP1559V2-notice__dialog">
<button
onClick={setEnableEIP1559V2NoticeDismissed}
className="enableEIP1559V2-notice__close-button"
data-testid="enableEIP1559V2-notice-close"
/>
<Box display={DISPLAY.FLEX}>
<Box paddingTop={2}>
<i style={{ fontSize: '1rem' }} className="fa fa-info-circle" />
</Box>
<Box paddingLeft={4}>
<Typography
color={COLORS.BLACK}
align={TEXT_ALIGN.LEFT}
variant={TYPOGRAPHY.H7}
fontWeight={FONT_WEIGHT.BOLD}
>
{t('enableEIP1559V2Header')}
</Typography>
<Typography
color={COLORS.BLACK}
align={TEXT_ALIGN.LEFT}
variant={TYPOGRAPHY.H7}
boxProps={{ marginBottom: 2 }}
>
{t('enableEIP1559V2AlertMessage')}
</Typography>
<Button
type="link"
onClick={() => {
history.push(EXPERIMENTAL_ROUTE);
}}
className="enableEIP1559V2-notice__link"
>
{t('enableEIP1559V2ButtonText')}
</Button>
</Box>
</Box>
</Dialog>
</Box>
);
}
EnableEIP1559V2Notice.propTypes = {
isFirstAlert: PropTypes.bool,
};

@ -0,0 +1 @@
export { default } from './enableEIP1559V2-notice';

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

Loading…
Cancel
Save