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

feature/default_network_editable
ryanml 3 years ago
commit c6a6e6c21b
  1. 10
      .circleci/config.yml
  2. 3
      .circleci/scripts/chrome-install.sh
  3. 4
      .storybook/test-data.js
  4. 1615
      CHANGELOG.md
  5. 2
      README.md
  6. 6
      app/_locales/am/messages.json
  7. 6
      app/_locales/ar/messages.json
  8. 6
      app/_locales/bg/messages.json
  9. 6
      app/_locales/bn/messages.json
  10. 6
      app/_locales/ca/messages.json
  11. 6
      app/_locales/cs/messages.json
  12. 6
      app/_locales/da/messages.json
  13. 6
      app/_locales/de/messages.json
  14. 6
      app/_locales/el/messages.json
  15. 121
      app/_locales/en/messages.json
  16. 176
      app/_locales/es/messages.json
  17. 130
      app/_locales/es_419/messages.json
  18. 6
      app/_locales/et/messages.json
  19. 6
      app/_locales/fa/messages.json
  20. 6
      app/_locales/fi/messages.json
  21. 6
      app/_locales/fil/messages.json
  22. 6
      app/_locales/fr/messages.json
  23. 6
      app/_locales/he/messages.json
  24. 123
      app/_locales/hi/messages.json
  25. 6
      app/_locales/hn/messages.json
  26. 6
      app/_locales/hr/messages.json
  27. 6
      app/_locales/ht/messages.json
  28. 6
      app/_locales/hu/messages.json
  29. 123
      app/_locales/id/messages.json
  30. 6
      app/_locales/it/messages.json
  31. 130
      app/_locales/ja/messages.json
  32. 6
      app/_locales/kn/messages.json
  33. 399
      app/_locales/ko/messages.json
  34. 6
      app/_locales/lt/messages.json
  35. 6
      app/_locales/lv/messages.json
  36. 6
      app/_locales/ms/messages.json
  37. 6
      app/_locales/nl/messages.json
  38. 6
      app/_locales/no/messages.json
  39. 93
      app/_locales/ph/messages.json
  40. 6
      app/_locales/pl/messages.json
  41. 6
      app/_locales/pt/messages.json
  42. 141
      app/_locales/pt_BR/messages.json
  43. 6
      app/_locales/ro/messages.json
  44. 125
      app/_locales/ru/messages.json
  45. 6
      app/_locales/sk/messages.json
  46. 6
      app/_locales/sl/messages.json
  47. 6
      app/_locales/sr/messages.json
  48. 6
      app/_locales/sv/messages.json
  49. 6
      app/_locales/sw/messages.json
  50. 6
      app/_locales/ta/messages.json
  51. 6
      app/_locales/th/messages.json
  52. 6
      app/_locales/tl/messages.json
  53. 6
      app/_locales/tr/messages.json
  54. 6
      app/_locales/uk/messages.json
  55. 131
      app/_locales/vi/messages.json
  56. 6
      app/_locales/zh_CN/messages.json
  57. 6
      app/_locales/zh_TW/messages.json
  58. BIN
      app/images/logo.png
  59. 112
      app/images/transak.svg
  60. 1
      app/scripts/constants/on-ramp.js
  61. 18
      app/scripts/controllers/detect-tokens.test.js
  62. 34
      app/scripts/controllers/incoming-transactions.js
  63. 120
      app/scripts/controllers/incoming-transactions.test.js
  64. 71
      app/scripts/controllers/network/network-controller.test.js
  65. 83
      app/scripts/controllers/network/network.js
  66. 93
      app/scripts/controllers/preferences.js
  67. 146
      app/scripts/controllers/preferences.test.js
  68. 98
      app/scripts/controllers/transactions/index.js
  69. 15
      app/scripts/controllers/transactions/index.test.js
  70. 139
      app/scripts/controllers/transactions/lib/util.js
  71. 261
      app/scripts/controllers/transactions/lib/util.test.js
  72. 5
      app/scripts/controllers/transactions/tx-gas-utils.js
  73. 13
      app/scripts/controllers/transactions/tx-gas-utils.test.js
  74. 36
      app/scripts/controllers/transactions/tx-state-manager.js
  75. 131
      app/scripts/controllers/transactions/tx-state-manager.test.js
  76. 57
      app/scripts/lib/buy-eth-url.js
  77. 75
      app/scripts/lib/buy-eth-url.test.js
  78. 11
      app/scripts/lib/typed-message-manager.js
  79. 35
      app/scripts/lib/util.js
  80. 40
      app/scripts/lib/util.test.js
  81. 53
      app/scripts/metamask-controller.js
  82. 5
      app/scripts/metamask-controller.test.js
  83. 5
      development/build/scripts.js
  84. 2
      development/build/static.js
  85. 17
      development/build/task.js
  86. 16
      development/lib/exit-with-error.js
  87. 23
      development/lib/retry.js
  88. 214
      lavamoat/node/policy.json
  89. 23
      package.json
  90. 19
      patches/selenium-webdriver+4.0.0-alpha.7.patch
  91. 21
      shared/constants/network.js
  92. 27
      shared/constants/transaction.js
  93. 25
      shared/modules/buffer-utils.test.js
  94. 40
      shared/modules/fetch-with-timeout.test.js
  95. 19
      shared/modules/hexstring-utils.test.js
  96. 108
      test/e2e/benchmark.js
  97. 42
      test/e2e/helpers.js
  98. 271
      test/e2e/metamask-ui.spec.js
  99. 57
      test/e2e/run-all.js
  100. 30
      test/e2e/run-all.sh
  101. Some files were not shown because too many files have changed in this diff Show More

@ -343,7 +343,7 @@ jobs:
command: |
if .circleci/scripts/test-run-e2e.sh
then
yarn test:e2e:chrome
yarn test:e2e:chrome --retries 2
fi
no_output_timeout: 20m
- store_artifacts:
@ -370,7 +370,7 @@ jobs:
command: |
if .circleci/scripts/test-run-e2e.sh
then
yarn test:e2e:chrome:metrics
yarn test:e2e:chrome:metrics --retries 2
fi
no_output_timeout: 20m
- store_artifacts:
@ -397,7 +397,7 @@ jobs:
command: |
if .circleci/scripts/test-run-e2e.sh
then
yarn test:e2e:firefox
yarn test:e2e:firefox --retries 2
fi
no_output_timeout: 20m
- store_artifacts:
@ -424,7 +424,7 @@ jobs:
command: |
if .circleci/scripts/test-run-e2e.sh
then
yarn test:e2e:firefox:metrics
yarn test:e2e:firefox:metrics --retries 2
fi
no_output_timeout: 20m
- store_artifacts:
@ -448,7 +448,7 @@ jobs:
command: mv ./builds-test ./builds
- run:
name: Run page load benchmark
command: yarn benchmark:chrome --out test-artifacts/chrome/benchmark/pageload.json
command: yarn benchmark:chrome --out test-artifacts/chrome/benchmark/pageload.json --retries 2
- store_artifacts:
path: test-artifacts
destination: test-artifacts

@ -14,7 +14,10 @@ wget -O "${CHROME_BINARY}" -t 5 "${CHROME_BINARY_URL}"
if [[ $(shasum -a 512 "${CHROME_BINARY}" | cut '--delimiter= ' -f1) != "${CHROME_BINARY_SHA512SUM}" ]]
then
echo "Google Chrome binary checksum did not match."
exit 1
else
echo "Google Chrome binary checksum verified."
fi
(sudo dpkg -i "${CHROME_BINARY}" || sudo apt-get -fy install)

@ -260,7 +260,7 @@ const state = {
}
},
"assetImages": {
"0xad6d458402f60fd3bd25163575031acdce07538d": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xaD6D458402F60fD3Bd25163575031ACDce07538D/logo.png"
"0xad6d458402f60fd3bd25163575031acdce07538d": "./images/logo.png"
},
"hiddenTokens": [],
"suggestedTokens": {},
@ -271,7 +271,7 @@ const state = {
"ipfsGateway": "dweb.link",
"infuraBlocked": false,
"migratedPrivacyMode": false,
"selectedAddress": "0x64a845a5b02460acf8a3d84503b0d68d028b4bb4",
"selectedAddress": "0x9d0ba4ddac06032527b140912ec808ab9451b788",
"metaMetricsId": "0xc2377d11fec1c3b7dd88c4854240ee5e3ed0d9f63b00456d98d80320337b827f",
"conversionDate": 1620710825.03,
"conversionRate": 3910.28,

File diff suppressed because it is too large Load Diff

@ -5,7 +5,7 @@ Hey! We are hiring JavaScript Engineers! [Apply here](https://boards.greenhouse.
You can find the latest version of MetaMask on [our official website](https://metamask.io/). For help using MetaMask, visit our [User Support Site](https://metamask.zendesk.com/hc/en-us).
For [general questions](https://metamask.zendesk.com/hc/en-us/community/topics/360000682532-General), [feature requests](https://metamask.zendesk.com/hc/en-us/community/topics/360000682552-Feature-Requests-Ideas), or [developer questions](https://metamask.zendesk.com/hc/en-us/community/topics/360001751291-Developer-Questions), visit our [Community Forum](https://metamask.zendesk.com/hc/en-us/community/topics).
For [general questions](https://community.metamask.io/c/learn/26), [feature requests](https://community.metamask.io/c/feature-requests-ideas/13), or [developer questions](https://community.metamask.io/c/developer-questions/11), visit our [Community Forum](https://community.metamask.io/).
MetaMask supports Firefox, Google Chrome, and Chromium-based browsers. We recommend using the latest available browser version.

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "ማሰሺያዎት አልተደገፈም..."
},
"builtInCalifornia": {
"message": "MetaMask ካሊፎርኒያ ውስጥ ተዘጋጅቶ የተገነባ ነው።"
},
"buyWithWyre": {
"message": "ETH በ Wyre ይግዙ"
},
@ -751,9 +748,6 @@
"recents": {
"message": "የቅርብ ጊዜያት"
},
"recipientAddress": {
"message": "የተቀባይ አድራሻ"
},
"recipientAddressPlaceholder": {
"message": "ፍለጋ፣ ለሕዝብ ክፍት የሆነ አድራሻ (0x), ወይም ENS"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "متصفحك غير مدعوم..."
},
"builtInCalifornia": {
"message": "تم تصميم وإنشاء MetaMask في ولاية كاليفورنيا."
},
"buyWithWyre": {
"message": "قم بشراء عملة إيثير بواسطة Wyre"
},
@ -747,9 +744,6 @@
"recents": {
"message": "الحديث"
},
"recipientAddress": {
"message": "عنوان المستلم"
},
"recipientAddressPlaceholder": {
"message": "البحث، العنوان العام (0x)، أو ENS"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "Браузърът ви не се поддържа ..."
},
"builtInCalifornia": {
"message": "MetaMask е проектиран и създаден в Калифорния."
},
"buyWithWyre": {
"message": "Купете ETH с Wyre"
},
@ -750,9 +747,6 @@
"recents": {
"message": "Скорошни"
},
"recipientAddress": {
"message": "Адрес на получателя"
},
"recipientAddressPlaceholder": {
"message": "Търсене, публичен адрес (0x) или ENS"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "আপনর বউজর সমরিত নয়..."
},
"builtInCalifornia": {
"message": "MetaMask কিিিইন কর এবিিত।"
},
"buyWithWyre": {
"message": "Wyre দি ETH করয় করন"
},
@ -754,9 +751,6 @@
"recents": {
"message": "সরতিকগি"
},
"recipientAddress": {
"message": "পপকর ঠি"
},
"recipientAddressPlaceholder": {
"message": "অনসনন, সবজনন ঠি (0x), ব ENS"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "El teu navegador no és suportat..."
},
"builtInCalifornia": {
"message": "MetaMask ha estat dissenyat i desenvolupat a Califòrnia."
},
"buyWithWyre": {
"message": "Compra ETH amb Wyre"
},
@ -732,9 +729,6 @@
"readdToken": {
"message": "Pots tornar a afegir aquesta fitxa en el futur anant a \"Afegir fitxa\" al menu d'opcions dels teus comptes."
},
"recipientAddress": {
"message": "Adreça del destinatari"
},
"recipientAddressPlaceholder": {
"message": "Cerca, adreça pública (0x), o ENS"
},

@ -46,9 +46,6 @@
"blockiesIdenticon": {
"message": "Použít Blockies Identicon"
},
"builtInCalifornia": {
"message": "MetaMask je navržen a vytvořen v Kalifornii."
},
"cancel": {
"message": "Zrušit"
},
@ -308,9 +305,6 @@
"readdToken": {
"message": "Tento token můžete v budoucnu přidat zpět s „Přidat token“ v nastavení účtu."
},
"recipientAddress": {
"message": "Adresa příjemce"
},
"reject": {
"message": "Odmítnout"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "Din browser er ikke understøttet..."
},
"builtInCalifornia": {
"message": "MetaMask er designet og bygget i Californien."
},
"buyWithWyre": {
"message": "Køb ETH med Wyre"
},
@ -735,9 +732,6 @@
"recents": {
"message": "Seneste"
},
"recipientAddress": {
"message": "Modtagerens adresse"
},
"recipientAddressPlaceholder": {
"message": "Søg, offentlig adresse (0x) eller ENS"
},

@ -140,9 +140,6 @@
"browserNotSupported": {
"message": "Ihr Browser wird nicht unterstützt …"
},
"builtInCalifornia": {
"message": "MetaMask wurde in Kalifornien entwickelt und gebaut."
},
"buyWithWyre": {
"message": "ETH mit Wyre kaufen"
},
@ -723,9 +720,6 @@
"recents": {
"message": "Letzte"
},
"recipientAddress": {
"message": "Empfängeradresse"
},
"recipientAddressPlaceholder": {
"message": "Suchen, öffentliche Adresse (0x) oder ENS"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "Το Πρόγραμμα Περιήγησής σας δεν υποστηρίζεται..."
},
"builtInCalifornia": {
"message": "Το MetaMask έχει σχεδιαστεί και αναπτυχθεί στην Καλιφόρνια."
},
"buyWithWyre": {
"message": "Αγοράστε ETH με το Wyre"
},
@ -751,9 +748,6 @@
"recents": {
"message": "Πρόσφατα"
},
"recipientAddress": {
"message": "Διεύθυνση Παραλήπτη"
},
"recipientAddressPlaceholder": {
"message": "Αναζήτηση, δημόσια διεύθυνση (0x) ή ENS"
},

@ -97,6 +97,9 @@
"addTokens": {
"message": "Add Tokens"
},
"addressBookIcon": {
"message": "Address book icon"
},
"advanced": {
"message": "Advanced"
},
@ -150,6 +153,9 @@
"amount": {
"message": "Amount"
},
"amountGasFee": {
"message": "Amount + Gas Fee"
},
"amountWithColon": {
"message": "Amount:"
},
@ -223,7 +229,7 @@
"message": "This secret code is required to recover your wallet in case you lose your device, forget your password, have to re-install MetaMask, or want to access your wallet on another device."
},
"backupApprovalNotice": {
"message": "Backup your Secret Recovery code to keep your wallet and funds secure."
"message": "Backup your Secret Recovery Phrase to keep your wallet and funds secure."
},
"backupNow": {
"message": "Backup now"
@ -253,15 +259,21 @@
"browserNotSupported": {
"message": "Your Browser is not supported..."
},
"builContactList": {
"buildContactList": {
"message": "Build your contact list"
},
"builtInCalifornia": {
"message": "MetaMask is designed and built in California."
"builtAroundTheWorld": {
"message": "MetaMask is designed and built around the world."
},
"buy": {
"message": "Buy"
},
"buyWithTransak": {
"message": "Buy ETH with Transak"
},
"buyWithTransakDescription": {
"message": "Transak supports debit card and bank transfers (depending on location) in 59+ countries. ETH deposits into your MetaMask account."
},
"buyWithWyre": {
"message": "Buy ETH with Wyre"
},
@ -414,6 +426,9 @@
"continue": {
"message": "Continue"
},
"continueToTransak": {
"message": "Continue to Transak"
},
"continueToWyre": {
"message": "Continue to Wyre"
},
@ -492,6 +507,9 @@
"customToken": {
"message": "Custom Token"
},
"data": {
"message": "Data"
},
"dataBackupFoundInfo": {
"message": "Some of your account data was backed up during a previous installation of MetaMask. This could include your settings, contacts, and tokens. Would you like to restore this data now?"
},
@ -599,6 +617,39 @@
"editContact": {
"message": "Edit Contact"
},
"editGasEducationButtonText": {
"message": "How should I choose?"
},
"editGasEducationHighExplanation": {
"message": "This is best for swaps or other time sensitive transactions. If a swap takes too long to process it will often fail and you may lose funds."
},
"editGasEducationLearnMoreLinkText": {
"message": "Learn more about customizing gas."
},
"editGasEducationLowExplanation": {
"message": "Low A lower gas fee should only be selected for transactions where processing time is less important. With a lower fee, it can be be hard to predict when (or if) your transaction with be successful."
},
"editGasEducationMediumExplanation": {
"message": "A medium gas fee is good for sending, withdrawing or other non-time sensitive but important transactions."
},
"editGasEducationModalIntro": {
"message": "The right gas amount to select depends on the type of transaction and how important it is."
},
"editGasEducationModalTitle": {
"message": "How to choose?"
},
"editGasHigh": {
"message": "High"
},
"editGasLow": {
"message": "Low"
},
"editGasMedium": {
"message": "Medium"
},
"editGasTitle": {
"message": "Edit gas fee"
},
"editNonceField": {
"message": "Edit Nonce"
},
@ -650,12 +701,21 @@
"message": "The endpoint returned a different chain ID: $1",
"description": "$1 is the return value of eth_chainId from an RPC endpoint"
},
"ensIllegalCharacter": {
"message": "Illegal Character for ENS."
},
"ensNotFoundOnCurrentNetwork": {
"message": "ENS name not found on the current network. Try switching to Ethereum Mainnet."
},
"ensNotSupportedOnNetwork": {
"message": "Network does not support ENS"
},
"ensRegistrationError": {
"message": "Error in ENS name registration"
},
"ensUnknownError": {
"message": "ENS Lookup failed."
},
"enterAnAlias": {
"message": "Enter an alias"
},
@ -777,6 +837,19 @@
"functionType": {
"message": "Function Type"
},
"gasDisplayAcknowledgeDappButtonText": {
"message": "Edit app suggestion"
},
"gasDisplayDappWarning": {
"message": "This gas fee has been suggested by the app $1. It’s using legacy gas estimation which may be inaccurate. However, editing this gas fee may cause unintended consequences. Please reach out to the app team if you have questions.",
"description": "$1 represents the Dapp's origin"
},
"gasFee": {
"message": "Gas Fee"
},
"gasFeeEstimate": {
"message": "Estimate"
},
"gasLimit": {
"message": "Gas Limit"
},
@ -1064,6 +1137,12 @@
"max": {
"message": "Max"
},
"maxFee": {
"message": "Max fee"
},
"maxPriorityFee": {
"message": "Max priority fee"
},
"memo": {
"message": "memo"
},
@ -1355,6 +1434,9 @@
"onlyConnectTrust": {
"message": "Only connect with sites you trust."
},
"optional": {
"message": "Optional"
},
"optionalBlockExplorerUrl": {
"message": "Block Explorer URL (optional)"
},
@ -1451,12 +1533,12 @@
"recents": {
"message": "Recents"
},
"recipientAddress": {
"message": "Recipient Address"
},
"recipientAddressPlaceholder": {
"message": "Search, public address (0x), or ENS"
},
"recommendedGasLabel": {
"message": "Recommended"
},
"recoveryPhraseReminderBackupStart": {
"message": "Start here"
},
@ -1775,6 +1857,15 @@
"speedUpCancellation": {
"message": "Speed up this cancellation"
},
"speedUpExplanation": {
"message": "We’ve restimated the gas fee based on current network conditions and have increased it by at least 10% (required by the network)."
},
"speedUpPopoverTitle": {
"message": "Speed up transaction"
},
"speedUpTooltipText": {
"message": "New gas fee"
},
"speedUpTransaction": {
"message": "Speed up this transaction"
},
@ -2189,6 +2280,9 @@
"symbolBetweenZeroTwelve": {
"message": "Symbol must be 11 characters or fewer."
},
"syncInProgress": {
"message": "Sync in progress"
},
"syncWithMobile": {
"message": "Sync with mobile"
},
@ -2280,6 +2374,12 @@
"transactionCreated": {
"message": "Transaction created with a value of $1 at $2."
},
"transactionDetailGasHeading": {
"message": "Estimated gas fee"
},
"transactionDetailGasTotalSubtitle": {
"message": "Amount + gas fee"
},
"transactionDropped": {
"message": "Transaction dropped at $2."
},
@ -2369,6 +2469,10 @@
"message": "verify the network details",
"description": "Serves as link text for the 'unrecognizedChain' key. This text will be embedded inside the translation for that key."
},
"unsendableAsset": {
"message": "Sending collectible (ERC-721) tokens is not currently supported",
"description": "This is an error message we show the user if they attempt to send a collectible asset type, for which currently don't support sending"
},
"updatedWithDate": {
"message": "Updated $1"
},
@ -2411,6 +2515,9 @@
"viewContact": {
"message": "View Contact"
},
"viewFullTransactionDetails": {
"message": "View full transaction details"
},
"viewMore": {
"message": "View More"
},

@ -6,7 +6,7 @@
"message": "Versión, centro de soporte técnico e información de contacto"
},
"acceleratingATransaction": {
"message": "* Usar un precio de gas más alto para acelerar una transacción aumenta las posibilidades de un procesamiento más rápido en la red, pero esto no siempre se garantiza."
"message": "* Usar un precio de gas más alto para acelerar una transacción aumenta las posibilidades de un procesamiento más rápido en la red, pero esto no siempre se garantiza."
},
"acceptTermsOfUse": {
"message": "Leí y estoy de acuerdo con $1",
@ -52,6 +52,10 @@
"addContact": {
"message": "Agregar contacto"
},
"addCustomTokenByContractAddress": {
"message": "¿No encuentra un token? Puede agregar cualquier token si copia su dirección. Puede encontrar la dirección de contrato del token en $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Esto permitirá que la red se utilice en MetaMask."
},
@ -85,7 +89,7 @@
"message": "Agregar a la libreta de direcciones"
},
"addToAddressBookModalPlaceholder": {
"message": "p. ej., John D."
"message": "p. ej., John D."
},
"addToken": {
"message": "Agregar token"
@ -249,12 +253,9 @@
"browserNotSupported": {
"message": "El explorador no es compatible…"
},
"builContactList": {
"buildContactList": {
"message": "Cree su lista de contactos"
},
"builtInCalifornia": {
"message": "MetaMask se diseñó y compiló en California."
},
"buy": {
"message": "Comprar"
},
@ -268,7 +269,7 @@
"message": "Bytes"
},
"canToggleInSettings": {
"message": "Puede volver a activar esta notificación desde Configuración > Alertas."
"message": "Puede volver a activar esta notificación desde Configuración -> Alertas."
},
"cancel": {
"message": "Cancelar"
@ -285,8 +286,11 @@
"chainIdDefinition": {
"message": "El identificador de cadena que se utiliza para firmar transacciones en esta red."
},
"chainIdExistsErrorMsg": {
"message": "En este momento, la red $1 está utilizando este identificador de cadena."
},
"chromeRequiredForHardwareWallets": {
"message": "Debe usar MetaMask en Google Chrome para poder conectarse a su cartera de hardware."
"message": "Debe usar MetaMask en Google Chrome para poder conectarse a su cartera de hardware."
},
"clickToRevealSeed": {
"message": "Haga clic aquí para revelar las palabras secretas"
@ -410,6 +414,9 @@
"continueToWyre": {
"message": "Continuar a Wyre"
},
"contract": {
"message": "Contrato"
},
"contractAddressError": {
"message": "Está enviando tokens a la dirección de contrato del token. Esto puede provocar la pérdida de los tokens."
},
@ -572,7 +579,7 @@
"message": "No volver a mostrar"
},
"downloadGoogleChrome": {
"message": "Descargar Google Chrome"
"message": "Descargar Google Chrome"
},
"downloadSecretBackup": {
"message": "Descargue esta frase secreta de respaldo y guárdela en un medio de almacenamiento o disco duro externo cifrado."
@ -626,6 +633,10 @@
"endOfFlowMessage6": {
"message": "Si necesita volver a crear una copia de seguridad de la frase secreta de recuperación, puede encontrarla en Configuración -> Seguridad."
},
"endOfFlowMessage7": {
"message": "Si tiene preguntas o nota movimientos sospechosos, comuníquese con soporte técnico $1.",
"description": "$1 is a clickable link with text defined by the 'here' key. The link will open to a form where users can file support tickets."
},
"endOfFlowMessage8": {
"message": "MetaMask no puede recuperar la frase secreta de recuperación."
},
@ -770,7 +781,7 @@
"message": "El límite de gas es la cantidad máxima de unidades de gas que está dispuesto a gastar."
},
"gasLimitTooLow": {
"message": "El límite de gas debe ser al menos 21 000"
"message": "El límite de gas debe ser al menos 21 000"
},
"gasLimitTooLowWithDynamicFee": {
"message": "El límite de gas debe ser al menos $1",
@ -885,6 +896,16 @@
"importAccountSeedPhrase": {
"message": "Importar una cuenta con la frase secreta de recuperación"
},
"importAccountText": {
"message": "o $1",
"description": "$1 represents the text from `importAccountLinkText` as a link"
},
"importTokenQuestion": {
"message": "¿Desea importar el token?"
},
"importTokenWarning": {
"message": "Toda persona puede crear un token con cualquier nombre, incluso versiones falsas de tokens existentes. ¡Agréguelo y realice transacciones bajo su propio riesgo!"
},
"importWallet": {
"message": "Importar cartera"
},
@ -956,7 +977,7 @@
"message": "Número no válido. Quite todos los ceros iniciales."
},
"invalidRPC": {
"message": "Dirección URL de RPC no válida"
"message": "Dirección URL de RPC no válida"
},
"invalidSeedPhrase": {
"message": "Frase secreta de recuperación no válida"
@ -1023,7 +1044,7 @@
"message": "Cargando tokens…"
},
"localhost": {
"message": "Host local 8545"
"message": "Host local 8545"
},
"lock": {
"message": "Bloquear"
@ -1110,7 +1131,7 @@
"message": "Escriba su contraseña para confirmar que es usted."
},
"mustSelectOne": {
"message": "Debe seleccionar al menos 1 token."
"message": "Debe seleccionar al menos 1 token."
},
"myAccounts": {
"message": "Mis cuentas"
@ -1160,10 +1181,10 @@
"message": "Agregar y editar redes RPC personalizadas"
},
"networkURL": {
"message": "Dirección URL de la red"
"message": "Dirección URL de la red"
},
"networkURLDefinition": {
"message": "La dirección URL que se utilizó para acceder a esta red."
"message": "La dirección URL que se utilizó para acceder a esta red."
},
"networks": {
"message": "Redes"
@ -1191,7 +1212,7 @@
"message": "Red nueva"
},
"newPassword": {
"message": "Contraseña nueva (mín. de 8 caracteres)"
"message": "Contraseña nueva (mín. de 8 caracteres)"
},
"newToMetaMask": {
"message": "¿Es nuevo en MetaMask?"
@ -1287,6 +1308,22 @@
"message": "Su \"frase de recuperación\" ahora se llama \"frase secreta de recuperación.\"",
"description": "Description of a notification in the 'See What's New' popup. Describes the seed phrase wording update."
},
"notifications6DescriptionOne": {
"message": "A partir de la versión 91 de Chrome, la API que habilitaba nuestro soporte para Ledger (U2F) ya no es compatible con carteras de hardware. MetaMask ha implementado un nuevo soporte para Ledger Live mediante el cual usted puede seguir conectándose a su dispositivo Ledger a través de la aplicación de escritorio Ledger Live.",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionThree": {
"message": "Cuando interactúe con su cuenta de Ledger a través de MetaMask, se abrirá una nueva pestaña y se le pedirá que abra la aplicación Ledger Live. Una vez que se abra la aplicación, se le pedirá que otorgue permiso para establecer una conexión WebSocket con su cuenta de MetaMask. ¡Eso es todo!",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionTwo": {
"message": "A fin de habilitar el soporte para Live Ledger, haga clic en Configuración > Avanzada > Utilizar Ledger Live.",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6Title": {
"message": "Actualización del soporte para Ledger destinada a usuarios de Chrome",
"description": "Title for a notification in the 'See What's New' popup. Lets users know about the Ledger support update"
},
"ofTextNofM": {
"message": "de"
},
@ -1372,7 +1409,7 @@
"message": "Moneda principal"
},
"primaryCurrencySettingDescription": {
"message": "Seleccione Nativa para dar prioridad a mostrar los valores en la moneda nativa de la cadena (p. ej., ETH). Seleccione Fiduciaria para dar prioridad a mostrar los valores en la moneda fiduciaria seleccionada."
"message": "Seleccione Nativa para dar prioridad a mostrar los valores en la moneda nativa de la cadena (p. ej., ETH). Seleccione Fiduciaria para dar prioridad a mostrar los valores en la moneda fiduciaria seleccionada."
},
"privacyMsg": {
"message": "Política de privacidad"
@ -1411,12 +1448,33 @@
"recents": {
"message": "Recientes"
},
"recipientAddress": {
"message": "Dirección del destinatario"
},
"recipientAddressPlaceholder": {
"message": "Búsqueda, dirección pública (0x) o ENS"
},
"recoveryPhraseReminderBackupStart": {
"message": "Iniciar aquí"
},
"recoveryPhraseReminderConfirm": {
"message": "Entendido"
},
"recoveryPhraseReminderHasBackedUp": {
"message": "Guarde siempre su frase secreta de recuperación en un lugar seguro y secreto."
},
"recoveryPhraseReminderHasNotBackedUp": {
"message": "¿Necesita volver a crear una copia de seguridad de su frase secreta de recuperación?"
},
"recoveryPhraseReminderItemOne": {
"message": "No comparta nunca su frase secreta de recuperación con nadie."
},
"recoveryPhraseReminderItemTwo": {
"message": "El equipo de MetaMask nunca le pedirá su frase secreta de recuperación."
},
"recoveryPhraseReminderSubText": {
"message": "Mediante su frase secreta de recuperación, se controlan todas sus cuentas."
},
"recoveryPhraseReminderTitle": {
"message": "Proteja sus fondos."
},
"reject": {
"message": "Rechazar"
},
@ -1424,7 +1482,7 @@
"message": "Rechazar todo"
},
"rejectTxsDescription": {
"message": "Está a punto de rechazar $1 transacciones en lote."
"message": "Está a punto de rechazar $1 transacciones en lote."
},
"rejectTxsN": {
"message": "Rechazar $1 transacciones"
@ -1442,7 +1500,7 @@
"message": "Quitar cuenta"
},
"removeAccountDescription": {
"message": "Esta cuenta se quitará de la cartera. Antes de continuar, asegúrese de tener la frase secreta de recuperación original o la clave privada de esta cuenta importada. Puede importar o crear cuentas nuevamente desde el menú desplegable de la cuenta."
"message": "Esta cuenta se quitará de la cartera. Antes de continuar, asegúrese de tener la frase secreta de recuperación original o la clave privada de esta cuenta importada. Puede importar o crear cuentas nuevamente en la lista desplegable de la cuenta. "
},
"requestsAwaitingAcknowledgement": {
"message": "solicitudes en espera de confirmación"
@ -1539,11 +1597,47 @@
"message": "Ingrese su frase secreta aquí para restaurar su bóveda."
},
"securityAndPrivacy": {
"message": "Seguridad y privacidad"
"message": "Seguridad y privacidad"
},
"securitySettingsDescription": {
"message": "Configuración de privacidad y frase secreta de recuperación de la cartera"
},
"seedPhraseIntroSidebarBulletFour": {
"message": "Escríbala y guárdela en varios lugares secretos."
},
"seedPhraseIntroSidebarBulletOne": {
"message": "Guárdela en un administrador de contraseñas"
},
"seedPhraseIntroSidebarBulletThree": {
"message": "Guárdela en una caja fuerte."
},
"seedPhraseIntroSidebarBulletTwo": {
"message": "Guárdela en una bóveda bancaria."
},
"seedPhraseIntroSidebarCopyOne": {
"message": "Su frase secreta de recuperación es la “llave maestra” de su cartera y sus fondos."
},
"seedPhraseIntroSidebarCopyThree": {
"message": "Si alguien le pide su frase de recuperación, es posible que tenga intenciones de estafarlo."
},
"seedPhraseIntroSidebarCopyTwo": {
"message": "Nunca comparta su frase secreta de recuperación, ni siquiera con MetaMask."
},
"seedPhraseIntroSidebarTitleOne": {
"message": "¿Qué es una frase de recuperación?"
},
"seedPhraseIntroSidebarTitleThree": {
"message": "¿Debería compartir mi frase de recuperación?"
},
"seedPhraseIntroSidebarTitleTwo": {
"message": "¿Cómo guardo mi frase de recuperación?"
},
"seedPhraseIntroTitle": {
"message": "Proteger su cartera"
},
"seedPhraseIntroTitleCopy": {
"message": "Antes de comenzar, mire este breve video para aprender sobre su frase de recuperación y sobre cómo mantener segura su cartera."
},
"seedPhrasePlaceholder": {
"message": "Separar cada palabra con un solo espacio"
},
@ -1551,7 +1645,7 @@
"message": "Pegar la frase secreta de recuperación desde el Portapapeles"
},
"seedPhraseReq": {
"message": "Las frases secretas de recuperación contienen 12, 15, 18, 21 o 24 palabras"
"message": "Las frases secretas de recuperación contienen 12, 15, 18, 21 o 24 palabras"
},
"selectAHigherGasFee": {
"message": "Seleccione una cuota de gas más alta para acelerar el procesamiento de la transacción.*"
@ -1870,7 +1964,7 @@
"message": "Cuota de MetaMask"
},
"swapMetaMaskFeeDescription": {
"message": "Buscamos el mejor precio en las fuentes de liquidez más importantes, todo el tiempo. Se incorpora de manera automática a esta cotización una cuota del $1 %.",
"message": "Buscamos el mejor precio en las fuentes de liquidez más importantes, todo el tiempo. Se incorpora de manera automática a esta cotización una cuota del $1 %.",
"description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number."
},
"swapNQuotes": {
@ -1893,9 +1987,18 @@
"description": "This message represents the price slippage for the swap. $1 and $4 are a number (ex: 2.89), $2 and $5 are symbols (ex: ETH), and $3 and $6 are fiat currency amounts."
},
"swapPriceDifferenceTitle": {
"message": "Diferencia de precio de ~$1 %",
"message": "Diferencia de precio de ~$1 %",
"description": "$1 is a number (ex: 1.23) that represents the price difference."
},
"swapPriceImpactTooltip": {
"message": "El impacto sobre el precio es la diferencia entre el precio actual del mercado y el monto recibido durante la ejecución de la transacción. El impacto sobre el precio es una función del tamaño de su transacción respecto de la dimensión del fondo de liquidez."
},
"swapPriceUnavailableDescription": {
"message": "No se pudo determinar el impacto sobre el precio debido a la falta de datos de los precios del mercado. Antes de realizar el canje, confirme que está de acuerdo con la cantidad de tokens que está a punto de recibir."
},
"swapPriceUnavailableTitle": {
"message": "Antes de continuar, verifique su tasa"
},
"swapProcessing": {
"message": "Procesamiento"
},
@ -1906,7 +2009,7 @@
"message": "Si el precio cambia entre el momento en que hace el pedido y cuando se confirma, se denomina \"desfase\". El canje se cancelará automáticamente si el desfase supera lo establecido en la configuración \"tolerancia de desfase\"."
},
"swapQuoteIncludesRate": {
"message": "La cotización incluye una cuota de MetaMask de $1 %",
"message": "La cotización incluye una cuota de MetaMask de $1 %",
"description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number."
},
"swapQuoteNofN": {
@ -1997,6 +2100,9 @@
"message": "Canjear $1 por $2",
"description": "Used in the transaction display list to describe a swap. $1 and $2 are the symbols of tokens in involved in a swap."
},
"swapTokenVerificationAddedManually": {
"message": "Este token se añadió de forma manual."
},
"swapTokenVerificationMessage": {
"message": "Siempre confirme la dirección del token en $1.",
"description": "Points the user to Etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"Etherscan\" followed by an info icon that shows more info on hover."
@ -2012,7 +2118,7 @@
"message": "Transacción completa"
},
"swapTwoTransactions": {
"message": "2 transacciones"
"message": "2 transacciones"
},
"swapUnknown": {
"message": "Desconocido"
@ -2029,13 +2135,13 @@
"description": "Tells the user how much of a token they have in their balance. $1 is a decimal number amount of tokens, and $2 is a token symbol"
},
"swapZeroSlippage": {
"message": "0 % de desfase"
"message": "0 % de desfase"
},
"swapsAdvancedOptions": {
"message": "Opciones avanzadas"
},
"swapsExcessiveSlippageWarning": {
"message": "El monto del desfase es muy alto, por lo que recibirá una tasa de conversión desfavorable. Disminuya su tolerancia de desfase a un valor menor al 15 %."
"message": "El monto del desfase es muy alto, por lo que recibirá una tasa de conversión desfavorable. Disminuya su tolerancia de desfase a un valor menor al 15 %."
},
"swapsMaxSlippage": {
"message": "Tolerancia de desfase"
@ -2075,7 +2181,7 @@
"message": "Símbolo"
},
"symbolBetweenZeroTwelve": {
"message": "El símbolo debe tener 11 caracteres o menos."
"message": "El símbolo debe tener 11 caracteres o menos."
},
"syncWithMobile": {
"message": "Sincronizar con dispositivo móvil"
@ -2264,7 +2370,7 @@
"message": "Las direcciones URL requieren el prefijo HTTP/HTTPS adecuado."
},
"urlExistsErrorMsg": {
"message": "La dirección URL ya está en la lista de redes existentes"
"message": "En este momento, la red $1 está utilizando esta dirección URL."
},
"usePhishingDetection": {
"message": "Usar detección de phishing"
@ -2286,6 +2392,10 @@
"message": "Comprobar este token en $1",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"verifyThisUnconfirmedTokenOn": {
"message": "Verifique este token en $1 y asegúrese de que sea el token con el que quiere realizar la transacción.",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"viewAccount": {
"message": "Ver cuenta"
},
@ -2320,7 +2430,7 @@
"message": "Frase secreta de recuperación de la cartera"
},
"web3ShimUsageNotification": {
"message": "Parece que el sitio web actual intentó utilizar la API de window.web3 que se eliminó. Si el sitio no funciona, haga clic en $1 para obtener más información.",
"message": "Parece que el sitio web actual intentó utilizar la API de window.web3 que se eliminó. Si el sitio no funciona, haga clic en $1 para obtener más información.",
"description": "$1 is a clickable link."
},
"welcome": {

@ -6,7 +6,7 @@
"message": "Versión, centro de soporte técnico e información de contacto"
},
"acceleratingATransaction": {
"message": "* Usar un precio de gas más alto para acelerar una transacción aumenta las posibilidades de un procesamiento más rápido en la red, pero esto no siempre se garantiza."
"message": "* Usar un precio de gas más alto para acelerar una transacción aumenta las posibilidades de un procesamiento más rápido en la red, pero esto no siempre se garantiza."
},
"acceptTermsOfUse": {
"message": "Leí y estoy de acuerdo con $1",
@ -52,6 +52,10 @@
"addContact": {
"message": "Agregar contacto"
},
"addCustomTokenByContractAddress": {
"message": "¿No encuentra un token? Puede agregar cualquier token si copia su dirección. Puede encontrar la dirección de contrato del token en $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Esto permitirá que la red se utilice en MetaMask."
},
@ -85,7 +89,7 @@
"message": "Agregar a la libreta de direcciones"
},
"addToAddressBookModalPlaceholder": {
"message": "p. ej., John D."
"message": "p. ej., John D."
},
"addToken": {
"message": "Agregar token"
@ -249,12 +253,9 @@
"browserNotSupported": {
"message": "El explorador no es compatible…"
},
"builContactList": {
"buildContactList": {
"message": "Cree su lista de contactos"
},
"builtInCalifornia": {
"message": "MetaMask se diseñó y compiló en California."
},
"buy": {
"message": "Comprar"
},
@ -268,7 +269,7 @@
"message": "Bytes"
},
"canToggleInSettings": {
"message": "Puede volver a activar esta notificación desde Configuración > Alertas."
"message": "Puede volver a activar esta notificación desde Configuración -> Alertas."
},
"cancel": {
"message": "Cancelar"
@ -285,8 +286,11 @@
"chainIdDefinition": {
"message": "El identificador de cadena que se utiliza para firmar transacciones en esta red."
},
"chainIdExistsErrorMsg": {
"message": "En este momento, la red $1 está utilizando este identificador de cadena."
},
"chromeRequiredForHardwareWallets": {
"message": "Debe usar MetaMask en Google Chrome para poder conectarse a su cartera de hardware."
"message": "Debe usar MetaMask en Google Chrome para poder conectarse a su cartera de hardware."
},
"clickToRevealSeed": {
"message": "Haga clic aquí para revelar las palabras secretas"
@ -410,6 +414,9 @@
"continueToWyre": {
"message": "Continuar a Wyre"
},
"contract": {
"message": "Contrato"
},
"contractAddressError": {
"message": "Está enviando tokens a la dirección de contrato del token. Esto puede provocar la pérdida de los tokens."
},
@ -572,7 +579,7 @@
"message": "No volver a mostrar"
},
"downloadGoogleChrome": {
"message": "Descargar Google Chrome"
"message": "Descargar Google Chrome"
},
"downloadSecretBackup": {
"message": "Descargue esta frase secreta de respaldo y guárdela en un medio de almacenamiento o disco duro externo cifrado."
@ -774,7 +781,7 @@
"message": "El límite de gas es la cantidad máxima de unidades de gas que está dispuesto a gastar."
},
"gasLimitTooLow": {
"message": "El límite de gas debe ser al menos 21 000"
"message": "El límite de gas debe ser al menos 21 000"
},
"gasLimitTooLowWithDynamicFee": {
"message": "El límite de gas debe ser al menos $1",
@ -893,6 +900,12 @@
"message": "o $1",
"description": "$1 represents the text from `importAccountLinkText` as a link"
},
"importTokenQuestion": {
"message": "¿Desea importar el token?"
},
"importTokenWarning": {
"message": "Toda persona puede crear un token con cualquier nombre, incluso versiones falsas de tokens existentes. ¡Agréguelo y realice transacciones bajo su propio riesgo!"
},
"importWallet": {
"message": "Importar cartera"
},
@ -964,7 +977,7 @@
"message": "Número no válido. Quite todos los ceros iniciales."
},
"invalidRPC": {
"message": "Dirección URL de RPC no válida"
"message": "Dirección URL de RPC no válida"
},
"invalidSeedPhrase": {
"message": "Frase secreta de recuperación no válida"
@ -1031,7 +1044,7 @@
"message": "Cargando tokens…"
},
"localhost": {
"message": "Host local 8545"
"message": "Host local 8545"
},
"lock": {
"message": "Bloquear"
@ -1118,7 +1131,7 @@
"message": "Escriba su contraseña para confirmar que es usted."
},
"mustSelectOne": {
"message": "Debe seleccionar al menos 1 token."
"message": "Debe seleccionar al menos 1 token."
},
"myAccounts": {
"message": "Mis cuentas"
@ -1168,10 +1181,10 @@
"message": "Agregar y editar redes RPC personalizadas"
},
"networkURL": {
"message": "Dirección URL de la red"
"message": "Dirección URL de la red"
},
"networkURLDefinition": {
"message": "La dirección URL que se utilizó para acceder a esta red."
"message": "La dirección URL que se utilizó para acceder a esta red."
},
"networks": {
"message": "Redes"
@ -1199,7 +1212,7 @@
"message": "Red nueva"
},
"newPassword": {
"message": "Contraseña nueva (mín. de 8 caracteres)"
"message": "Contraseña nueva (mín. de 8 caracteres)"
},
"newToMetaMask": {
"message": "¿Es nuevo en MetaMask?"
@ -1295,6 +1308,22 @@
"message": "Su \"frase de recuperación\" ahora se llama \"frase secreta de recuperación.\"",
"description": "Description of a notification in the 'See What's New' popup. Describes the seed phrase wording update."
},
"notifications6DescriptionOne": {
"message": "A partir de la versión 91 de Chrome, la API que habilitaba nuestro soporte para Ledger (U2F) ya no es compatible con carteras de hardware. MetaMask ha implementado un nuevo soporte para Ledger Live mediante el cual usted puede seguir conectándose a su dispositivo Ledger a través de la aplicación de escritorio Ledger Live.",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionThree": {
"message": "Cuando interactúe con su cuenta de Ledger a través de MetaMask, se abrirá una nueva pestaña y se le pedirá que abra la aplicación Ledger Live. Una vez que se abra la aplicación, se le pedirá que otorgue permiso para establecer una conexión WebSocket con su cuenta de MetaMask. ¡Eso es todo!",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionTwo": {
"message": "A fin de habilitar el soporte para Live Ledger, haga clic en Configuración > Avanzada > Utilizar Ledger Live.",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6Title": {
"message": "Actualización del soporte para Ledger destinada a usuarios de Chrome",
"description": "Title for a notification in the 'See What's New' popup. Lets users know about the Ledger support update"
},
"ofTextNofM": {
"message": "de"
},
@ -1380,7 +1409,7 @@
"message": "Moneda principal"
},
"primaryCurrencySettingDescription": {
"message": "Seleccione Nativa para dar prioridad a mostrar los valores en la moneda nativa de la cadena (p. ej., ETH). Seleccione Fiduciaria para dar prioridad a mostrar los valores en la moneda fiduciaria seleccionada."
"message": "Seleccione Nativa para dar prioridad a mostrar los valores en la moneda nativa de la cadena (p. ej., ETH). Seleccione Fiduciaria para dar prioridad a mostrar los valores en la moneda fiduciaria seleccionada."
},
"privacyMsg": {
"message": "Política de privacidad"
@ -1419,12 +1448,33 @@
"recents": {
"message": "Recientes"
},
"recipientAddress": {
"message": "Dirección del destinatario"
},
"recipientAddressPlaceholder": {
"message": "Búsqueda, dirección pública (0x) o ENS"
},
"recoveryPhraseReminderBackupStart": {
"message": "Iniciar aquí"
},
"recoveryPhraseReminderConfirm": {
"message": "Entendido"
},
"recoveryPhraseReminderHasBackedUp": {
"message": "Guarde siempre su frase secreta de recuperación en un lugar seguro y secreto."
},
"recoveryPhraseReminderHasNotBackedUp": {
"message": "¿Necesita volver a crear una copia de seguridad de su frase secreta de recuperación?"
},
"recoveryPhraseReminderItemOne": {
"message": "No comparta nunca su frase secreta de recuperación con nadie."
},
"recoveryPhraseReminderItemTwo": {
"message": "El equipo de MetaMask nunca le pedirá su frase secreta de recuperación."
},
"recoveryPhraseReminderSubText": {
"message": "Mediante su frase secreta de recuperación, se controlan todas sus cuentas."
},
"recoveryPhraseReminderTitle": {
"message": "Proteja sus fondos."
},
"reject": {
"message": "Rechazar"
},
@ -1432,7 +1482,7 @@
"message": "Rechazar todo"
},
"rejectTxsDescription": {
"message": "Está a punto de rechazar $1 transacciones en lote."
"message": "Está a punto de rechazar $1 transacciones en lote."
},
"rejectTxsN": {
"message": "Rechazar $1 transacciones"
@ -1547,7 +1597,7 @@
"message": "Ingrese su frase secreta aquí para restaurar su bóveda."
},
"securityAndPrivacy": {
"message": "Seguridad y privacidad"
"message": "Seguridad y privacidad"
},
"securitySettingsDescription": {
"message": "Configuración de privacidad y frase secreta de recuperación de la cartera"
@ -1595,7 +1645,7 @@
"message": "Pegar la frase secreta de recuperación desde el Portapapeles"
},
"seedPhraseReq": {
"message": "Las frases secretas de recuperación contienen 12, 15, 18, 21 o 24 palabras"
"message": "Las frases secretas de recuperación contienen 12, 15, 18, 21 o 24 palabras"
},
"selectAHigherGasFee": {
"message": "Seleccione una cuota de gas más alta para acelerar el procesamiento de la transacción.*"
@ -1914,7 +1964,7 @@
"message": "Cuota de MetaMask"
},
"swapMetaMaskFeeDescription": {
"message": "Buscamos el mejor precio en las fuentes de liquidez más importantes, todo el tiempo. Se incorpora de manera automática a esta cotización una cuota del $1 %.",
"message": "Buscamos el mejor precio en las fuentes de liquidez más importantes, todo el tiempo. Se incorpora de manera automática a esta cotización una cuota del $1 %.",
"description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number."
},
"swapNQuotes": {
@ -1937,9 +1987,18 @@
"description": "This message represents the price slippage for the swap. $1 and $4 are a number (ex: 2.89), $2 and $5 are symbols (ex: ETH), and $3 and $6 are fiat currency amounts."
},
"swapPriceDifferenceTitle": {
"message": "Diferencia de precio de ~$1 %",
"message": "Diferencia de precio de ~$1 %",
"description": "$1 is a number (ex: 1.23) that represents the price difference."
},
"swapPriceImpactTooltip": {
"message": "El impacto sobre el precio es la diferencia entre el precio actual del mercado y el monto recibido durante la ejecución de la transacción. El impacto sobre el precio es una función del tamaño de su transacción respecto de la dimensión del fondo de liquidez."
},
"swapPriceUnavailableDescription": {
"message": "No se pudo determinar el impacto sobre el precio debido a la falta de datos de los precios del mercado. Antes de realizar el canje, confirme que está de acuerdo con la cantidad de tokens que está a punto de recibir."
},
"swapPriceUnavailableTitle": {
"message": "Antes de continuar, verifique su tasa"
},
"swapProcessing": {
"message": "Procesamiento"
},
@ -1950,7 +2009,7 @@
"message": "Si el precio cambia entre el momento en que hace el pedido y cuando se confirma, se denomina \"desfase\". El canje se cancelará automáticamente si el desfase supera lo establecido en la configuración \"tolerancia de desfase\"."
},
"swapQuoteIncludesRate": {
"message": "La cotización incluye una cuota de MetaMask de $1 %",
"message": "La cotización incluye una cuota de MetaMask de $1 %",
"description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number."
},
"swapQuoteNofN": {
@ -2041,6 +2100,9 @@
"message": "Canjear $1 por $2",
"description": "Used in the transaction display list to describe a swap. $1 and $2 are the symbols of tokens in involved in a swap."
},
"swapTokenVerificationAddedManually": {
"message": "Este token se añadió de forma manual."
},
"swapTokenVerificationMessage": {
"message": "Siempre confirme la dirección del token en $1.",
"description": "Points the user to Etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"Etherscan\" followed by an info icon that shows more info on hover."
@ -2056,7 +2118,7 @@
"message": "Transacción completa"
},
"swapTwoTransactions": {
"message": "2 transacciones"
"message": "2 transacciones"
},
"swapUnknown": {
"message": "Desconocido"
@ -2073,13 +2135,13 @@
"description": "Tells the user how much of a token they have in their balance. $1 is a decimal number amount of tokens, and $2 is a token symbol"
},
"swapZeroSlippage": {
"message": "0 % de desfase"
"message": "0 % de desfase"
},
"swapsAdvancedOptions": {
"message": "Opciones avanzadas"
},
"swapsExcessiveSlippageWarning": {
"message": "El monto del desfase es muy alto, por lo que recibirá una tasa de conversión desfavorable. Disminuya su tolerancia de desfase a un valor menor al 15 %."
"message": "El monto del desfase es muy alto, por lo que recibirá una tasa de conversión desfavorable. Disminuya su tolerancia de desfase a un valor menor al 15 %."
},
"swapsMaxSlippage": {
"message": "Tolerancia de desfase"
@ -2119,7 +2181,7 @@
"message": "Símbolo"
},
"symbolBetweenZeroTwelve": {
"message": "El símbolo debe tener 11 caracteres o menos."
"message": "El símbolo debe tener 11 caracteres o menos."
},
"syncWithMobile": {
"message": "Sincronizar con dispositivo móvil"
@ -2308,7 +2370,7 @@
"message": "Las direcciones URL requieren el prefijo HTTP/HTTPS adecuado."
},
"urlExistsErrorMsg": {
"message": "La dirección URL ya está en la lista de redes existentes"
"message": "En este momento, la red $1 está utilizando esta dirección URL."
},
"usePhishingDetection": {
"message": "Usar detección de phishing"
@ -2330,6 +2392,10 @@
"message": "Comprobar este token en $1",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"verifyThisUnconfirmedTokenOn": {
"message": "Verifique este token en $1 y asegúrese de que sea el token con el que quiere realizar la transacción.",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"viewAccount": {
"message": "Ver cuenta"
},
@ -2364,7 +2430,7 @@
"message": "Frase secreta de recuperación de la cartera"
},
"web3ShimUsageNotification": {
"message": "Parece que el sitio web actual intentó utilizar la API de window.web3 que se eliminó. Si el sitio no funciona, haga clic en $1 para obtener más información.",
"message": "Parece que el sitio web actual intentó utilizar la API de window.web3 que se eliminó. Si el sitio no funciona, haga clic en $1 para obtener más información.",
"description": "$1 is a clickable link."
},
"welcome": {

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "Teie lehitsejat ei toetata..."
},
"builtInCalifornia": {
"message": "MetaMask on projekteeritud ja loodud Californias."
},
"buyWithWyre": {
"message": "Ostke ETH-d Wyre'iga"
},
@ -744,9 +741,6 @@
"recents": {
"message": "Hiljutised"
},
"recipientAddress": {
"message": "Saaja aadress"
},
"recipientAddressPlaceholder": {
"message": "Otsing, avalik aadress (0x) või ENS"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "مرورگر شما پشتیبانی نمیشود"
},
"builtInCalifornia": {
"message": "MetaMask در کالیفورنیا طراحی و ساخته شده است."
},
"buyWithWyre": {
"message": "ETH را توسط Wyre خریداری نمایید"
},
@ -754,9 +751,6 @@
"recents": {
"message": "واپسین"
},
"recipientAddress": {
"message": "آدرس دریافت کننده"
},
"recipientAddressPlaceholder": {
"message": "جستجو، آدرس عمومی (0x)، یا ENS"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "Selaintasi ei tueta..."
},
"builtInCalifornia": {
"message": "MetaMask on suunniteltu ja koottu Kaliforniassa."
},
"buyWithWyre": {
"message": "Osta ETH:ta Wyrella"
},
@ -751,9 +748,6 @@
"recents": {
"message": "Viimeaikaiset"
},
"recipientAddress": {
"message": "Vastaanottajan osoite"
},
"recipientAddressPlaceholder": {
"message": "Haku, julkinen osoite (0x) tai ENS"
},

@ -128,9 +128,6 @@
"browserNotSupported": {
"message": "Hindi sinusuportahan ang iyong Browser..."
},
"builtInCalifornia": {
"message": "Ang MetaMask ay dinisenyo at binuo sa California."
},
"buyWithWyre": {
"message": "Bumili ng ETH gamit ang Wyre"
},
@ -678,9 +675,6 @@
"recents": {
"message": "Kamakailan"
},
"recipientAddress": {
"message": "Address ng Recipient"
},
"recipientAddressPlaceholder": {
"message": "Maghanap, pampublikong address (0x), o ENS"
},

@ -137,9 +137,6 @@
"browserNotSupported": {
"message": "Votre navigateur internet n'est pas supporté..."
},
"builtInCalifornia": {
"message": "MetaMask est designé et developpé en Californie."
},
"buyWithWyre": {
"message": "Acheter ETH avec Wyre"
},
@ -736,9 +733,6 @@
"recents": {
"message": "Récents"
},
"recipientAddress": {
"message": "Adresse du destinataire"
},
"recipientAddressPlaceholder": {
"message": "Recherche, adresse publique (0x) ou ENS"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "הדפדפן שלך אינו נתמך..."
},
"builtInCalifornia": {
"message": "MetaMask תוכנן ונבנה בקליפורניה."
},
"buyWithWyre": {
"message": "רכישת את'ר עם Wyre"
},
@ -751,9 +748,6 @@
"recents": {
"message": "אחרונים"
},
"recipientAddress": {
"message": "כתובת הנמען"
},
"recipientAddressPlaceholder": {
"message": "חיפוש, כתובת ציבורית (0x), או ENS"
},

@ -52,6 +52,10 @@
"addContact": {
"message": "सपरक ज"
},
"addCustomTokenByContractAddress": {
"message": "टकन नहिल रह? आप अपन पतिपककर मअल रप सिकन क सकत। टकन अनध पत $1 पर मिल सकत।",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "इसस इस नटवरक क MetaMask कदर उपयग करन अनमतिि।"
},
@ -249,12 +253,9 @@
"browserNotSupported": {
"message": "आपकउजर समरित नह..."
},
"builContactList": {
"buildContactList": {
"message": "अपनपरक स बन"
},
"builtInCalifornia": {
"message": "MetaMask किििइन और निित कि गय।"
},
"buy": {
"message": "खर"
},
@ -285,6 +286,9 @@
"chainIdDefinition": {
"message": "इस नटवरक किए लन-दन पर हसषर करनिए उपयग कन ID।"
},
"chainIdExistsErrorMsg": {
"message": "यह चन ID वरतमन म $1 नटवरक द उपयग कि।"
},
"chromeRequiredForHardwareWallets": {
"message": "अपनडवयर वट स कनट करनिए आपक Google Chrome पर MetaMask क उपयग करन आवशयकत।"
},
@ -410,6 +414,9 @@
"continueToWyre": {
"message": "Wyre पर ज रख"
},
"contract": {
"message": "अनध"
},
"contractAddressError": {
"message": "आप टकन क अनध पत पर टकन भज रह। इसक परिमसवरप इन टकनकसन ह सकत।"
},
@ -624,7 +631,11 @@
"message": "फििग सवधन रह! MetaMask कभ अनस ह आपकत रिकवर नह।"
},
"endOfFlowMessage6": {
"message": "यदि आपक अपनत रिकवरिर सकअप ल आवशयकत, त आप इसिस -> सरक सकत।"
"message": "यदि आपक अपनत रिकवरिर सकअप ल आवशयकत, त आप इसिग -> सरक सकत।"
},
"endOfFlowMessage7": {
"message": "यदि आपक कभछ पछनछ गडबड लग, त हम सहयत $1 सपरक कर।",
"description": "$1 is a clickable link with text defined by the 'here' key. The link will open to a form where users can file support tickets."
},
"endOfFlowMessage8": {
"message": "MetaMask आपकत रिकवरनरत नह कर सकत।"
@ -885,6 +896,16 @@
"importAccountSeedPhrase": {
"message": "गत रिकवरथ एक ख आयत कर"
},
"importAccountText": {
"message": "य $1",
"description": "$1 represents the text from `importAccountLinkText` as a link"
},
"importTokenQuestion": {
"message": "टकन क आयत कर?"
},
"importTokenWarning": {
"message": "कई भिम कथ एक टकन बन सकत, जिसमकन क नकलकरण शिल ह। अपनिम पर ज और वर कर!"
},
"importWallet": {
"message": "वट आयत कर"
},
@ -1287,6 +1308,22 @@
"message": "आपक \"सड फ\" क अब आपक \"गत रिकवर\" कह।",
"description": "Description of a notification in the 'See What's New' popup. Describes the seed phrase wording update."
},
"notifications6DescriptionOne": {
"message": "Chrome ककरण 91 स, वह API ज हम Ledger सपट (U2F) क सकषम करत वह अब हडवयर वट क समरथन नह करत। MetaMask न एक नय Ledger Live सपट लि, जिसक मदद स आप Ledger Live डकटप ऐप कयम स अपन Ledger डिइस स कनट करन रख सकत।",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionThree": {
"message": "MetaMask म अपन Ledger ख पर कम करत समय, एक नयब खल जएग और आपक Ledger Live ऐप खलनिए कहएग। ऐप खलनद, आपक अपन MetaMask खिए एक WebSocket कनशन क अनमतििए कहएग। बस इतन!",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionTwo": {
"message": "आप सिग > उननत > Ledger Live क उपयग कर पर किक करक Ledger Live सहयत सकषम कर सकत।",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6Title": {
"message": "Chrome उपयगकरिए Ledger सहयत अदयतन",
"description": "Title for a notification in the 'See What's New' popup. Lets users know about the Ledger support update"
},
"ofTextNofM": {
"message": "/"
},
@ -1411,12 +1448,33 @@
"recents": {
"message": "हल ह"
},
"recipientAddress": {
"message": "पतकर पत"
},
"recipientAddressPlaceholder": {
"message": "खज, सवजनिक पत (0x) य ENS"
},
"recoveryPhraseReminderBackupStart": {
"message": "यहभ कर"
},
"recoveryPhraseReminderConfirm": {
"message": "समझ गय"
},
"recoveryPhraseReminderHasBackedUp": {
"message": "अपनत रिकवर हमरकित और गत सन पर रख।"
},
"recoveryPhraseReminderHasNotBackedUp": {
"message": "अपनत रिकवरिर सकअप करन आवशयकत?"
},
"recoveryPhraseReminderItemOne": {
"message": "कभ अपनत रिकवरिथ स न कर"
},
"recoveryPhraseReminderItemTwo": {
"message": "MetaMask टम कभ आपकत रिकवर नह"
},
"recoveryPhraseReminderSubText": {
"message": "आपकत रिकवर आपक सभिित करत।"
},
"recoveryPhraseReminderTitle": {
"message": "अपन धन करकित रख"
},
"reject": {
"message": "असर कर"
},
@ -1544,6 +1602,42 @@
"securitySettingsDescription": {
"message": "गपनयतिस और वट कत रिकवर"
},
"seedPhraseIntroSidebarBulletFour": {
"message": "लिख ल और कई गत सर कर।"
},
"seedPhraseIntroSidebarBulletOne": {
"message": "पसवरड मजर म सह"
},
"seedPhraseIntroSidebarBulletThree": {
"message": "सििट बस मर कर।"
},
"seedPhraseIntroSidebarBulletTwo": {
"message": "बक कि रख।"
},
"seedPhraseIntroSidebarCopyOne": {
"message": "आपकिकवर आपकट और धन किए “मटर क” ह।"
},
"seedPhraseIntroSidebarCopyThree": {
"message": "यदिई वयकि आपकिकवरगत, त सबस अधिक सवनि आपकरयस कर रह।"
},
"seedPhraseIntroSidebarCopyTwo": {
"message": "कभ अपनिकवर न कर, MetaMask कथ भ नह!"
},
"seedPhraseIntroSidebarTitleOne": {
"message": "रिकवर?"
},
"seedPhraseIntroSidebarTitleThree": {
"message": "क अपनिकवर करनिए?"
},
"seedPhraseIntroSidebarTitleTwo": {
"message": "म अपनिकवर सह?"
},
"seedPhraseIntroTitle": {
"message": "अपनट करकित कर"
},
"seedPhraseIntroTitleCopy": {
"message": "शआत करन पहल, अपनिकवर और अपनट करकित रखन तरननिए यह छ-सि।"
},
"seedPhrasePlaceholder": {
"message": "परतक शबद क एक रिि अलग कर"
},
@ -2006,6 +2100,9 @@
"message": "$1 स $2 मप कर",
"description": "Used in the transaction display list to describe a swap. $1 and $2 are the symbols of tokens in involved in a swap."
},
"swapTokenVerificationAddedManually": {
"message": "इस टकन कअल रप स गय।"
},
"swapTokenVerificationMessage": {
"message": "हम $1 पर टकन पति कर।",
"description": "Points the user to Etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"Etherscan\" followed by an info icon that shows more info on hover."
@ -2273,7 +2370,7 @@
"message": "URL क उपयत HTTP/HTTPS उपसरग क आवशयकत।"
},
"urlExistsErrorMsg": {
"message": "URL नटवरक क पहलद ह"
"message": "यह URL वरतमन म $1 नटवरक द उपयग कि"
},
"usePhishingDetection": {
"message": "फििग डिशन क उपयग कर"
@ -2295,6 +2392,10 @@
"message": "इस टकन क $1 पर सतित कर",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"verifyThisUnconfirmedTokenOn": {
"message": "इस टकन क $1 पर सतित कर और सिित करि यह वहकन हिसस आप वर करनहत।",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"viewAccount": {
"message": "ख"
},
@ -2328,6 +2429,10 @@
"walletSeedRestore": {
"message": "वट कत रिकवर"
},
"web3ShimUsageNotification": {
"message": "हमनि वरतमन वबसइट न हटए गए window.web3 API क उपयग करनिश क। यदिइट म गडबड लगत, तपय अधिक जनकिए $1 पर किक कर।",
"description": "$1 is a clickable link."
},
"welcome": {
"message": "MetaMask म आपकगत ह"
},

@ -43,9 +43,6 @@
"blockiesIdenticon": {
"message": "बज पहचन क उपयग कर"
},
"builtInCalifornia": {
"message": "मक किििइन और बन गय।"
},
"cancel": {
"message": "रदद कर"
},
@ -285,9 +282,6 @@
"readdToken": {
"message": "आप अपनिकलप म .टकन ज. पर जकर भविय म इस टकन कपस ज सकत।"
},
"recipientAddress": {
"message": "पतकर पत"
},
"reject": {
"message": "असर"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "Vaš se preglednik ne podržava..."
},
"builtInCalifornia": {
"message": "MetaMask je osmišljen i izrađen u Kaliforniji."
},
"buyWithWyre": {
"message": "Kupi ETH Wyerom"
},
@ -747,9 +744,6 @@
"recents": {
"message": "Nedavno"
},
"recipientAddress": {
"message": "Adresa primatelja"
},
"recipientAddressPlaceholder": {
"message": "Pretraži, javne adrese (0x) ili ENS"
},

@ -73,9 +73,6 @@
"browserNotSupported": {
"message": "Navigatè ou a pa sipòte..."
},
"builtInCalifornia": {
"message": "MetaMask fèt e bati nan California."
},
"cancel": {
"message": "Anile"
},
@ -450,9 +447,6 @@
"readdToken": {
"message": "Ou ka ajoute token sa aprè sa ankò ou prale nan \"Ajoute token\" nan opsyon meni kont ou an."
},
"recipientAddress": {
"message": "Adrès pou resevwa"
},
"reject": {
"message": "Rejte"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "Az ön böngészője nem támogatott..."
},
"builtInCalifornia": {
"message": "A MetaMaskot Kaliforniában tervezték és hozták létre."
},
"buyWithWyre": {
"message": "Vásároljon ETH-t a Wyre-rel"
},
@ -747,9 +744,6 @@
"recents": {
"message": "Legutóbbiak"
},
"recipientAddress": {
"message": "Címzett címe"
},
"recipientAddressPlaceholder": {
"message": "Keresés, nyilvános cím (0x) vagy ENS"
},

@ -52,6 +52,10 @@
"addContact": {
"message": "Tambah kontak"
},
"addCustomTokenByContractAddress": {
"message": "Tidak dapat menemukan token? Anda dapat menambahkan token secara manual dengan menempelkan alamatnya. Alamat kontrak token dapat ditemukan di $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Ini akan memungkinkan jaringan ini digunakan dengan MetaMask."
},
@ -249,12 +253,9 @@
"browserNotSupported": {
"message": "Browser Anda tidak didukung..."
},
"builContactList": {
"buildContactList": {
"message": "Buat daftar kontak Anda"
},
"builtInCalifornia": {
"message": "MetaMask didesain dan didirikan di California."
},
"buy": {
"message": "Beli"
},
@ -285,6 +286,9 @@
"chainIdDefinition": {
"message": "ID rantai digunakan untuk menandatangani transaksi untuk jaringan ini."
},
"chainIdExistsErrorMsg": {
"message": "ID Rantai ini saat ini digunakan oleh jaringan $1."
},
"chromeRequiredForHardwareWallets": {
"message": "Anda perlu menggunakan MetaMask di Google Chrome untuk terhubung ke Dompet Perangkat Keras Anda."
},
@ -410,6 +414,9 @@
"continueToWyre": {
"message": "Lanjutkan ke Wyre"
},
"contract": {
"message": "Kontrak"
},
"contractAddressError": {
"message": "Anda mengirim token ke alamat kontrak token. Ini dapat mengakibatkan token ini hilang."
},
@ -626,6 +633,10 @@
"endOfFlowMessage6": {
"message": "Jika Anda perlu mencadangkan Frasa Pemulihan Rahasia lagi, Anda dapat menemukannya di Pengaturan -> Keamanan."
},
"endOfFlowMessage7": {
"message": "Jika Anda memiliki pertanyaan atau melihat sesuatu yang mencurigakan, hubungi dukungan $1 kami.",
"description": "$1 is a clickable link with text defined by the 'here' key. The link will open to a form where users can file support tickets."
},
"endOfFlowMessage8": {
"message": "MetaMask tidak dapat memulihkan Frasa Pemulihan Rahasia Anda."
},
@ -885,6 +896,16 @@
"importAccountSeedPhrase": {
"message": "Impor akun dengan Frasa Pemulihan Rahasia"
},
"importAccountText": {
"message": "atau $1",
"description": "$1 represents the text from `importAccountLinkText` as a link"
},
"importTokenQuestion": {
"message": "Impor token?"
},
"importTokenWarning": {
"message": "Siapa pun dapat membuat token dengan nama apa pun, termasuk versi palsu dari token yang ada. Tambahkan dan perdagangkan dengan risiko Anda sendiri!"
},
"importWallet": {
"message": "Impor dompet"
},
@ -1287,6 +1308,22 @@
"message": "\"Frasa Pemulihan\" Anda kini disebut \"Frasa Pemulihan Rahasia.\"",
"description": "Description of a notification in the 'See What's New' popup. Describes the seed phrase wording update."
},
"notifications6DescriptionOne": {
"message": "Pada Chrome versi 91, API yang memungkinkan dukungan Ledger (U2F) kami tidak lagi mendukung dompet perangkat keras. MetaMask telah menerapkan dukungan Ledger Live baru yang memungkinkan Anda terus terhubung ke perangkat Ledger Anda melalui aplikasi desktop Ledger Live.",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionThree": {
"message": "Saat berinteraksi dengan akun Ledger Anda di MetaMask, tab baru akan terbuka dan Anda akan diminta untuk membuka aplikasi Ledger Live. Setelah aplikasi tersebut terbuka, Anda akan diminta untuk mengizinkan koneksi WebSocket ke akun MetaMask Anda. Tidak diperlukan tindakan lebih lanjut.",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionTwo": {
"message": "Anda dapat mengaktifkan dukungan Ledger Live dengan mengklik Pengaturan > Lanjutan > Gunakan Ledger Live.",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6Title": {
"message": "Pembaruan Dukungan Ledger untuk Pengguna Chrome",
"description": "Title for a notification in the 'See What's New' popup. Lets users know about the Ledger support update"
},
"ofTextNofM": {
"message": "dari"
},
@ -1411,12 +1448,33 @@
"recents": {
"message": "Terkini"
},
"recipientAddress": {
"message": "Alamat Penerima"
},
"recipientAddressPlaceholder": {
"message": "Cari, alamat publik (0x), atau ENS"
},
"recoveryPhraseReminderBackupStart": {
"message": "Mulai di sini"
},
"recoveryPhraseReminderConfirm": {
"message": "Mengerti"
},
"recoveryPhraseReminderHasBackedUp": {
"message": "Jaga selalu Frasa Pemulihan Rahasia Anda di tempat yang aman dan rahasia"
},
"recoveryPhraseReminderHasNotBackedUp": {
"message": "Perlu mencadangkan Frasa Pemulihan Rahasia Anda lagi?"
},
"recoveryPhraseReminderItemOne": {
"message": "Jangan membagikan Frasa Pemulihan Rahasia Anda kepada siapa pun"
},
"recoveryPhraseReminderItemTwo": {
"message": "Tim MetaMask tidak akan pernah meminta Frasa Pemulihan Rahasia Anda"
},
"recoveryPhraseReminderSubText": {
"message": "Frasa Pemulihan Rahasia Anda mengendalikan semua akun Anda."
},
"recoveryPhraseReminderTitle": {
"message": "Lindungi dana Anda"
},
"reject": {
"message": "Tolak"
},
@ -1442,7 +1500,7 @@
"message": "Hapus akun"
},
"removeAccountDescription": {
"message": "Akun ini akan dihapus dari dompet Anda. Pastikan Anda memiliki Frasa Pemulihan Rahasia asli atau kunci privat untuk akun impor ini sebelum melanjutkan. Anda dapat mengimpor atau membuat akun lagi dari drop down akun. "
"message": "Akun ini akan dihapus dari dompet Anda. Pastikan Anda memiliki Frasa Pemulihan Rahasia asli atau kunci privat untuk akun impor ini sebelum melanjutkan. Anda dapat mengimpor atau membuat akun lagi dari akun drop down. "
},
"requestsAwaitingAcknowledgement": {
"message": "permintaan menunggu untuk diakui"
@ -1544,6 +1602,42 @@
"securitySettingsDescription": {
"message": "Pengaturan privasi dan Frasa Pemulihan Rahasia dompet"
},
"seedPhraseIntroSidebarBulletFour": {
"message": "Tuliskan dan simpan di beberapa tempat rahasia."
},
"seedPhraseIntroSidebarBulletOne": {
"message": "Simpan dalam pengelola kata sandi"
},
"seedPhraseIntroSidebarBulletThree": {
"message": "Simpan di kotak deposit yang aman."
},
"seedPhraseIntroSidebarBulletTwo": {
"message": "Simpan di vault bank."
},
"seedPhraseIntroSidebarCopyOne": {
"message": "Frasa pemulihan Anda adalah “kunci induk” ke dompet dan dana Anda."
},
"seedPhraseIntroSidebarCopyThree": {
"message": "Jika seseorang menanyakan frasa pemulihan Anda, kemungkinan mereka akan mencoba menipu Anda."
},
"seedPhraseIntroSidebarCopyTwo": {
"message": "Jangan pernah membagikan frasa pemulihan Anda bahkan kepada MetaMask!"
},
"seedPhraseIntroSidebarTitleOne": {
"message": "Apa itu frasa pemulihan?"
},
"seedPhraseIntroSidebarTitleThree": {
"message": "Haruskah saya membagikan frasa pemulihan saya?"
},
"seedPhraseIntroSidebarTitleTwo": {
"message": "Bagaimana cara menyimpan frasa pemulihan saya?"
},
"seedPhraseIntroTitle": {
"message": "Amankan dompet Anda"
},
"seedPhraseIntroTitleCopy": {
"message": "Sebelum memulai, lihat video singkat ini untuk mempelajari tentang frasa pemulihan Anda dan cara menjaga keamanan dompet Anda."
},
"seedPhrasePlaceholder": {
"message": "Pisahkan setiap kata dengan satu spasi"
},
@ -2006,6 +2100,9 @@
"message": "Tukar $1 untuk $2",
"description": "Used in the transaction display list to describe a swap. $1 and $2 are the symbols of tokens in involved in a swap."
},
"swapTokenVerificationAddedManually": {
"message": "Token ini telah ditambahkan secara manual."
},
"swapTokenVerificationMessage": {
"message": "Selalu konfirmasikan alamat token di $1.",
"description": "Points the user to Etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"Etherscan\" followed by an info icon that shows more info on hover."
@ -2273,7 +2370,7 @@
"message": "URL memerlukan awalan HTTP/HTTPS yang sesuai."
},
"urlExistsErrorMsg": {
"message": "URL sudah ada dalam daftar jaringan yang ada"
"message": "URL ini saat ini digunakan oleh jaringan $1."
},
"usePhishingDetection": {
"message": "Menggunakan Deteksi Phishing"
@ -2295,6 +2392,10 @@
"message": "Verifikasikan token ini di $1",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"verifyThisUnconfirmedTokenOn": {
"message": "Verifikasi token ini di $1 dan pastikan ini adalah token yang ingin Anda perdagangkan.",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"viewAccount": {
"message": "Lihat Akun"
},
@ -2328,6 +2429,10 @@
"walletSeedRestore": {
"message": "Frasa Pemulihan Rahasia Dompet"
},
"web3ShimUsageNotification": {
"message": "Kami melihat situs web saat ini mencoba menggunakan API window.web3 yang dihapus. Jika situs tersebut tampak bermasalah, silakan klik $1 untuk informasi selengkapnya.",
"description": "$1 is a clickable link."
},
"welcome": {
"message": "Selamat datang di MetaMask"
},

@ -217,9 +217,6 @@
"browserNotSupported": {
"message": "Il tuo Browser non è supportato..."
},
"builtInCalifornia": {
"message": "MetaMask è progettato e realizzato in California."
},
"buy": {
"message": "Compra"
},
@ -1201,9 +1198,6 @@
"recents": {
"message": "Recenti"
},
"recipientAddress": {
"message": "Indirizzo Destinatario"
},
"recipientAddressPlaceholder": {
"message": "Ricerca, indirizzo pubblico (0x) o ENS"
},

@ -52,6 +52,10 @@
"addContact": {
"message": "連絡先の追加"
},
"addCustomTokenByContractAddress": {
"message": "トークンを発見できませんか?アドレスをペーストすることで手動でトークンを追加することができます。トークン コントラクト アドレスは $1 にあります。",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "これにより、このネットワークは MetaMask 内で使用できるようになります。"
},
@ -109,7 +113,7 @@
"message": "アグリゲーター ネットワーク料金"
},
"alertDisableTooltip": {
"message": "\"設定 > 警告\" の設定で変更できます"
"message": "これは、[\"設定 > 警告\"] で変更できます"
},
"alertSettingsUnconnectedAccount": {
"message": "選択した未接続のアカウントを使用して Web サイトをブラウズしています"
@ -249,12 +253,9 @@
"browserNotSupported": {
"message": "ご使用のブラウザーはサポートされていません..."
},
"builContactList": {
"buildContactList": {
"message": "連絡先リストを作成する"
},
"builtInCalifornia": {
"message": "MetaMask はカリフォルニアで設計および作成されました。"
},
"buy": {
"message": "購入"
},
@ -285,6 +286,9 @@
"chainIdDefinition": {
"message": "このネットワークのトランザクションの署名に使用されるチェーン ID。"
},
"chainIdExistsErrorMsg": {
"message": "このチェーン ID は現在 $1 ネットワークで使用しています。"
},
"chromeRequiredForHardwareWallets": {
"message": "ハードウェア ウォレットに接続するには、MetaMask on Google Chrome を使用する必要があります。"
},
@ -410,6 +414,9 @@
"continueToWyre": {
"message": "Wyre に進む"
},
"contract": {
"message": "コントラクト"
},
"contractAddressError": {
"message": "トークンのコントラクト アドレスにトークンを送信しています。これにより、これらのトークンが失われる可能性があります。"
},
@ -624,7 +631,11 @@
"message": "フィッシングにご注意ください!MetaMask の動作として、シークレット リカバリー フレーズを要求することは絶対にありません。"
},
"endOfFlowMessage6": {
"message": "シークレット リカバリー フレーズを再度バックアップする場合は、[設定] -> [セキュリティとプライバシー] にアクセスしてください。"
"message": "シークレット リカバリー フレーズを再度バックアップする場合は、[設定] -> [セキュリティ] でそれを見つけることができます。"
},
"endOfFlowMessage7": {
"message": "ご質問、または不審な点がある場合は、当社のサポート $1 までお問い合わせください。",
"description": "$1 is a clickable link with text defined by the 'here' key. The link will open to a form where users can file support tickets."
},
"endOfFlowMessage8": {
"message": "MetaMask はシークレット リカバリー フレーズを復元できません。"
@ -885,6 +896,16 @@
"importAccountSeedPhrase": {
"message": "シークレット リカバリー フレーズを使用してアカウントをインポートする:"
},
"importAccountText": {
"message": "または $1",
"description": "$1 represents the text from `importAccountLinkText` as a link"
},
"importTokenQuestion": {
"message": "トークンをインポートしますか?"
},
"importTokenWarning": {
"message": "誰でも既存のトークンの偽バージョンを含めて、任意の名前でトークンを作成することができます。自己責任で追加およびトレードしてください。"
},
"importWallet": {
"message": "ウォレットのインポート"
},
@ -1287,6 +1308,22 @@
"message": "これで、\"シード フレーズ\" は \"シークレット リカバリー フレーズ\" と呼ばれます。",
"description": "Description of a notification in the 'See What's New' popup. Describes the seed phrase wording update."
},
"notifications6DescriptionOne": {
"message": "Chrome バージョン 91 以降は、レジャーのサポート (U2F) を可能にした API がハードウェア ウォレットをサポートしなくなります。MetaMask では、ユーザーがレジャー ライブのデスクトップ アプリを介して、レジャー デバイスに継続的に接続することができる新しいレジャー ライブのサポートを導入しました。",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionThree": {
"message": "MetaMask のレジャーのアカウントを使用する際は、新しいタブが開き、レジャー ライブのアプリを開くよう指示されます。アプリが開いたら、WebSocket 接続を MetaMask のアカウントに許可するよう指示されます。以上です。",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionTwo": {
"message": "[設定] > [詳細] > [レジャー ライブを使用] の順にクリックすることで、レジャー ライブのサポートを有効にすることができます。",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6Title": {
"message": "Chrome ユーザー向けのレジャーのサポートの更新",
"description": "Title for a notification in the 'See What's New' popup. Lets users know about the Ledger support update"
},
"ofTextNofM": {
"message": "中の"
},
@ -1411,12 +1448,33 @@
"recents": {
"message": "最近"
},
"recipientAddress": {
"message": "受信者のアドレス"
},
"recipientAddressPlaceholder": {
"message": "検索、パブリック アドレス (0x)、または ENS"
},
"recoveryPhraseReminderBackupStart": {
"message": "ここから開始"
},
"recoveryPhraseReminderConfirm": {
"message": "OK"
},
"recoveryPhraseReminderHasBackedUp": {
"message": "シークレット リカバリー フレーズは常に安全かつ秘密の場所に保管してください"
},
"recoveryPhraseReminderHasNotBackedUp": {
"message": "シークレット リカバリー フレーズのバックアップが必要ですか?"
},
"recoveryPhraseReminderItemOne": {
"message": "シークレット リカバリー フレーズは誰とも決して共有しないでください"
},
"recoveryPhraseReminderItemTwo": {
"message": "MetaMask チームが、ユーザーのシークレット リカバリー フレーズを確認することは絶対にありません"
},
"recoveryPhraseReminderSubText": {
"message": "シークレット リカバリー フレーズは、ご利用のすべてのアカウントを制御します。"
},
"recoveryPhraseReminderTitle": {
"message": "資産を保護してください"
},
"reject": {
"message": "拒否"
},
@ -1544,6 +1602,42 @@
"securitySettingsDescription": {
"message": "プライバシーの設定とシークレット リカバリー フレーズ"
},
"seedPhraseIntroSidebarBulletFour": {
"message": "書き留めて、複数の秘密の場所に保存します。"
},
"seedPhraseIntroSidebarBulletOne": {
"message": "パスワード マネージャーに保存する"
},
"seedPhraseIntroSidebarBulletThree": {
"message": "セーフティ ボックスに保管する。"
},
"seedPhraseIntroSidebarBulletTwo": {
"message": "銀行の金庫に保管する。"
},
"seedPhraseIntroSidebarCopyOne": {
"message": "あなたのリカバリー フレーズは、ウォレットと資金への「マスターキー」です。"
},
"seedPhraseIntroSidebarCopyThree": {
"message": "誰かがあなたのリカバリー フレーズを尋ねてきたら、おそらくあなたを騙そうとしているのです。"
},
"seedPhraseIntroSidebarCopyTwo": {
"message": "MetaMask を共有しても、リカバリ フレーズは決して共有しないでください。"
},
"seedPhraseIntroSidebarTitleOne": {
"message": "リカバリー フレーズとは何ですか?"
},
"seedPhraseIntroSidebarTitleThree": {
"message": "リカバリーフレーズは共有すべきですか?"
},
"seedPhraseIntroSidebarTitleTwo": {
"message": "リカバリー フレーズはどのように保存すべきですか?"
},
"seedPhraseIntroTitle": {
"message": "ウォレットの保護"
},
"seedPhraseIntroTitleCopy": {
"message": "始める前に、この短いビデオを見て、リカバリー フレーズとウォレットを安全に保つ方法について確認してください。"
},
"seedPhrasePlaceholder": {
"message": "単語ごとにスペースを 1 つ置いて分離します"
},
@ -1896,6 +1990,15 @@
"message": "約 $1% の価格差",
"description": "$1 is a number (ex: 1.23) that represents the price difference."
},
"swapPriceImpactTooltip": {
"message": "プライスインパクトとは、現在の市場価格と取引の約定時に受け取った金額の差のことです。プライスインパクトとは、流動性プールの大きさに対するあなたのトレードの大きさを表わす関数です。"
},
"swapPriceUnavailableDescription": {
"message": "市場価格のデータが不足しているため、プライスインパクトを測定できませんでした。スワップする前に、これから受領するトークンの額に問題がないか確認してください。"
},
"swapPriceUnavailableTitle": {
"message": "続行する前にレートを確認してください"
},
"swapProcessing": {
"message": "処理中"
},
@ -1997,6 +2100,9 @@
"message": "$1 を $2 にスワップ",
"description": "Used in the transaction display list to describe a swap. $1 and $2 are the symbols of tokens in involved in a swap."
},
"swapTokenVerificationAddedManually": {
"message": "このトークンは手動で追加されました。"
},
"swapTokenVerificationMessage": {
"message": "常に $1 のトークン アドレスを確認してください。",
"description": "Points the user to Etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"Etherscan\" followed by an info icon that shows more info on hover."
@ -2264,7 +2370,7 @@
"message": "URL には適切な HTTP/HTTPS プレフィックスが必要です。"
},
"urlExistsErrorMsg": {
"message": "URL はネットワークの既存のリストに既に存在します"
"message": "この URL は現在 $1 ネットワークで使用しています。"
},
"usePhishingDetection": {
"message": "フィッシング検出を使用"
@ -2286,6 +2392,10 @@
"message": "このトークンを $1 で検証",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"verifyThisUnconfirmedTokenOn": {
"message": "このトークンを $1 で検証して、それがトレードしたいトークンであることを確認してください。",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"viewAccount": {
"message": "アカウントを表示"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "ನಿಮ ಬಸರಬಲಿಿಲ..."
},
"builtInCalifornia": {
"message": "MetaMask ಅನಿಸಗಿಸಲಿ ಮತಿಿದಲಿಿಿಸಲಿ."
},
"buyWithWyre": {
"message": "Wyre ನಿ ETH ಖರಿಿ"
},
@ -754,9 +751,6 @@
"recents": {
"message": "ಇತಿನವಗಳ"
},
"recipientAddress": {
"message": "ಸಕರಿವವರ ವಿಸ"
},
"recipientAddressPlaceholder": {
"message": "ಸವಜನಿಕ ವಿಸ (0x) ಅಥವ ENS ಹಿ"
},

File diff suppressed because it is too large Load Diff

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "Jūsų naršyklė neatpažįstama..."
},
"builtInCalifornia": {
"message": "„MetaMask“ suprojektuota ir įdiegta Kalifornijoje."
},
"buyWithWyre": {
"message": "Pirkti ETH su „Wyre“"
},
@ -754,9 +751,6 @@
"recents": {
"message": "Naujausi"
},
"recipientAddress": {
"message": "Gavėjo adresas"
},
"recipientAddressPlaceholder": {
"message": "Ieška, viešieji adresai (0x) arba ENS"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "Jūsu pārlūkprogramma netiek atbalstīta..."
},
"builtInCalifornia": {
"message": "MetaMask ir izstrādāta un izveidota Kalifornijā."
},
"buyWithWyre": {
"message": "Pirkt ETH ar Wyre"
},
@ -750,9 +747,6 @@
"recents": {
"message": "Nesenie"
},
"recipientAddress": {
"message": "Saņēmēja adrese"
},
"recipientAddressPlaceholder": {
"message": "Meklēšana, publiskā adrese (0x) vai ENS"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "Pelayar anda tidak disokong..."
},
"builtInCalifornia": {
"message": "MetaMask direka dan dibina di California."
},
"buyWithWyre": {
"message": "Beli ETH dengan Wyre"
},
@ -731,9 +728,6 @@
"recents": {
"message": "Baru-baru ini"
},
"recipientAddress": {
"message": "Alamat Penerima"
},
"recipientAddressPlaceholder": {
"message": "Cari, alamat awam (0x), atau ENS"
},

@ -40,9 +40,6 @@
"blockiesIdenticon": {
"message": "Gebruik Blockies Identicon"
},
"builtInCalifornia": {
"message": "MetaMask is ontworpen en gebouwd in Californië."
},
"cancel": {
"message": "Annuleer"
},
@ -272,9 +269,6 @@
"readdToken": {
"message": "U kunt dit token in de toekomst weer toevoegen door naar \"Token toevoegen\" te gaan in het menu met accountopties."
},
"recipientAddress": {
"message": "Geadresseerde adres"
},
"reject": {
"message": "Afwijzen"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "Nettleseren din støttes ikke ..."
},
"builtInCalifornia": {
"message": "MetaMask ble bygget og designet i California."
},
"buyWithWyre": {
"message": "Kjøp ETH med Wyre"
},
@ -741,9 +738,6 @@
"recents": {
"message": "Nylige"
},
"recipientAddress": {
"message": "Mottakeradresse"
},
"recipientAddressPlaceholder": {
"message": "Søk, offentlig adresse (0x) eller ENS"
},

@ -6,7 +6,7 @@
"message": "Bersyon, support center, at impormasyon sa pakikipag-ugnayan"
},
"acceleratingATransaction": {
"message": "* Kapag in-accelerate ang transaksyon sa pamamagitan ng paggamit ng mas mataas na presyo ng gas, mas magiging malaki ang tsansang mas mabilis na maproseso ng network, pero hindi ito palaging ginagarantiya."
"message": "* Kapag in-accelerate ang transaksyon sa pamamagitan ng paggamit ng mas mataas na presyo ng gas, mas magiging malaki ang tsansang mas mabilis na maiproseso ng network, pero hindi ito palaging ginagarantiya."
},
"acceptTermsOfUse": {
"message": "Nabasa ko at sumasang-ayon ako sa $1",
@ -52,6 +52,10 @@
"addContact": {
"message": "Magdagdag ng contact"
},
"addCustomTokenByContractAddress": {
"message": "Walang makitang token? Puwede kang manual na magdagdag ng anumang token sa pamamagitan ng pag-paste ng address nito. Makikita ang mga address ng kontrata ng token sa $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Bibigyang-daan nito na magamit ang network na ito sa MetaMask."
},
@ -249,12 +253,9 @@
"browserNotSupported": {
"message": "Hindi sinusuportahan ang iyong Browser..."
},
"builContactList": {
"buildContactList": {
"message": "Buuin ang iyong listahan ng contact"
},
"builtInCalifornia": {
"message": "Ang MetaMask ay idinisenyo at binuo sa California."
},
"buy": {
"message": "Bumili"
},
@ -285,6 +286,9 @@
"chainIdDefinition": {
"message": "Ginagamit ang chain ID para maglagda ng mga transaksyon para sa network na ito."
},
"chainIdExistsErrorMsg": {
"message": "Kasalukuyang ginagamit ng $1 network ang Chain ID na ito."
},
"chromeRequiredForHardwareWallets": {
"message": "Kailangan mong gamitin ang MetaMask sa Google Chrome para maikonekta sa iyong Hardware Wallet."
},
@ -410,6 +414,9 @@
"continueToWyre": {
"message": "Magpatuloy sa Wyre"
},
"contract": {
"message": "Kontrata"
},
"contractAddressError": {
"message": "Magpapadala ka ng mga token sa address ng kontrata ng token. Posible itong magresulta sa pagkawala ng mga token na ito."
},
@ -641,7 +648,7 @@
"description": "$1 is the return value of eth_chainId from an RPC endpoint"
},
"ensNotFoundOnCurrentNetwork": {
"message": "Hindi nahanapa ang ENS name sa kasalukuyang network. Subukang lumipat sa Ethereum Mainnet."
"message": "Hindi nahanap ang ENS name sa kasalukuyang network. Subukang lumipat sa Ethereum Mainnet."
},
"ensRegistrationError": {
"message": "Nagka-error sa pag-register ng ENS name"
@ -694,7 +701,7 @@
"message": "Mga Tinatantyang Tagal ng Pagproseso"
},
"ethGasPriceFetchWarning": {
"message": "Ibinibigay ang backup na presyo ng gas dahil hindi available ang pangunahing serbisyo sa pagtatantiya ng gas sa ngayon."
"message": "Ibinibigay ang backup na presyo ng gas dahil hindi available ang pangunahing serbisyo sa pagtatantya ng gas sa ngayon."
},
"eth_accounts": {
"message": "Tingnan ang mga address ng iyong mga pinapayagang account (kinakailangan)",
@ -793,7 +800,7 @@
"message": "Sobrang Baba ng Presyo ng Gas"
},
"gasPriceFetchFailed": {
"message": "Hindi nagtagumpay ang pagtatantiya ng presyo ng gas dahil sa error sa network."
"message": "Hindi nagtagumpay ang pagtatantya ng presyo ng gas dahil sa error sa network."
},
"gasPriceInfoTooltipContent": {
"message": "Tinutukoy ng presyo ng gas ang halaga ng Ether na handa mong bayaran para sa bawat unit ng gas."
@ -893,6 +900,12 @@
"message": "o $1",
"description": "$1 represents the text from `importAccountLinkText` as a link"
},
"importTokenQuestion": {
"message": "Mag-import ng token?"
},
"importTokenWarning": {
"message": "Sinuman ay makakagawa ng token na may anumang pangalan, kasama ang mga pekeng bersyon ng mga token na mayroon na. Magdagdag at mag-trade sa sarili mong pananagutan!"
},
"importWallet": {
"message": "Mag-import ng wallet"
},
@ -1022,7 +1035,7 @@
"message": "Mga Link"
},
"loadMore": {
"message": "Matuto Pa"
"message": "Mag-load Pa"
},
"loading": {
"message": "Nilo-load..."
@ -1127,7 +1140,7 @@
"message": "Pangalan"
},
"needEtherInWallet": {
"message": "Para makaugnayan ang mga decentralized ma application gamit ang MetaMask, kakailanganin mo ang Ether sa iyong wallet."
"message": "Para makaugnayan ang mga decentralized na application gamit ang MetaMask, kakailanganin mo ang Ether sa iyong wallet."
},
"needHelp": {
"message": "Kailangan ng tulong? Makipag-ugnayan sa $1",
@ -1162,7 +1175,7 @@
"message": "Testnet"
},
"networkSettingsChainIdDescription": {
"message": "Ginagaamit ang chain ID sa paglagda ng mga transaksyon. Dapat itong tumugma sa chain ID na ibinalik ng network. Puwede kang maglagay ng decimal o '0x'-prefixed hexadecimal number, pero ipapakita namin ang numero sa decimal."
"message": "Ginagamit ang chain ID sa paglagda ng mga transaksyon. Dapat itong tumugma sa chain ID na ibinalik ng network. Puwede kang maglagay ng decimal o '0x'-prefixed hexadecimal number, pero ipapakita namin ang numero sa decimal."
},
"networkSettingsDescription": {
"message": "Magdagdag at mag-edit ng mga custom na RPC network"
@ -1214,7 +1227,7 @@
"message": "Susunod"
},
"nextNonceWarning": {
"message": "Mas mataas ang noncesa iminumungkahing nonce na $1",
"message": "Mas mataas ang nonce sa iminumungkahing nonce na $1",
"description": "The next nonce according to MetaMask's internal logic"
},
"noAccountsFound": {
@ -1295,6 +1308,22 @@
"message": "Tinatawag na ngayong \"Secret Recovery Phrase\" mo ang iyong \"Seed Phrase.\"",
"description": "Description of a notification in the 'See What's New' popup. Describes the seed phrase wording update."
},
"notifications6DescriptionOne": {
"message": "Simula sa Chrome version 91, hindi na susuportahan ng API na nag-enable sa aming Ledger support (U2F) ang mga hardware wallet. Nagpatupad ang MetaMask ng bagong Ledger Live support na nagbibigay-daan sa iyong patuloy na ikonekta ang Ledger device mo sa pamamagitan ng Ledger Live desktop app.",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionThree": {
"message": "Kapag ginagamit ang iyong Ledger account sa MetaMask, may bagong tab na magbubukas at hihilingin sa iyong buksan ang Ledger Live app. Kapag nagbukas ang app, hihilingin sa iyong payagan ang isang koneksyon ng WebSocket sa MetaMask account mo. Iyon lang!",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionTwo": {
"message": "Puwede mong i-enable ang Ledger Live support sa pamamagitan ng pag-click sa Mga Setting > Advanced > Gamitin ang Ledger Live.",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6Title": {
"message": "Update sa Ledger Support para sa Mga Chrome User",
"description": "Title for a notification in the 'See What's New' popup. Lets users know about the Ledger support update"
},
"ofTextNofM": {
"message": "ng"
},
@ -1419,12 +1448,33 @@
"recents": {
"message": "Mga Kamakailan"
},
"recipientAddress": {
"message": "Address ng Tatanggap"
},
"recipientAddressPlaceholder": {
"message": "Maghanap, pampublikong address (0x), o ENS"
},
"recoveryPhraseReminderBackupStart": {
"message": "Magsimula rito"
},
"recoveryPhraseReminderConfirm": {
"message": "OK"
},
"recoveryPhraseReminderHasBackedUp": {
"message": "Palaging panatilihin ang iyong Secret Recovery Phrase sa isang ligtas at lihim na lugar"
},
"recoveryPhraseReminderHasNotBackedUp": {
"message": "Kailangan ulit i-back up ang Secret Recovery Phrase mo?"
},
"recoveryPhraseReminderItemOne": {
"message": "Huwag kailanman ipaalam sa iba ang iyong Secret Recovery Phrase"
},
"recoveryPhraseReminderItemTwo": {
"message": "Hindi kailanman hihingin ng MetaMask team ang iyong Secret Recovery Phrase"
},
"recoveryPhraseReminderSubText": {
"message": "Kinokontrol ng iyong Secret Recovery Phrase ang lahat ng iyong account."
},
"recoveryPhraseReminderTitle": {
"message": "Protektahan ang iyong pondo!"
},
"reject": {
"message": "Tanggihan"
},
@ -1869,7 +1919,7 @@
"message": "Ito ay pagtatantya ng bayarin sa network na gagamitin para kumpletuhin ang iyong pag-swap. Posibleng magbago ang aktuwal na halaga ayon sa mga kundisyon ng network."
},
"swapFailedErrorDescriptionWithSupportLink": {
"message": "May mga hindi pagtatagumpay sa transkasyon na nangyayari at narito kami para tumulong. Kung magpapatuloy ang isyung ito, puwede kang makipag-ugnayan sa aming suporta sa customer sa $1 para sa karagdagang tulong.",
"message": "May mga hindi pagtatagumpay sa transaksyon na nangyayari at narito kami para tumulong. Kung magpapatuloy ang isyung ito, puwede kang makipag-ugnayan sa aming suporta sa customer sa $1 para sa karagdagang tulong.",
"description": "This message is shown to a user if their swap fails. The $1 will be replaced by support.metamask.io"
},
"swapFailedErrorTitle": {
@ -1901,7 +1951,7 @@
"message": "Posibleng hindi magtagumpay ang transaksyon, masyadong mababa ang max na slippage."
},
"swapMaxNetworkFeeInfo": {
"message": "Aang “$1” ay ang pinakamalaking gagastusin mo. Kapag volatile ang network, maaaring malaking halaga ito.",
"message": "“$1” ang pinakamalaking gagastusin mo. Kapag volatile ang network, maaaring malaking halaga ito.",
"description": "$1 will be the translation of swapMaxNetworkFees, with the font bolded"
},
"swapMaxNetworkFees": {
@ -2050,6 +2100,9 @@
"message": "I-swap ang $1 sa $2",
"description": "Used in the transaction display list to describe a swap. $1 and $2 are the symbols of tokens in involved in a swap."
},
"swapTokenVerificationAddedManually": {
"message": "Manual na idinagdag ang token na ito."
},
"swapTokenVerificationMessage": {
"message": "Palaging kumpirmahin ang address ng token sa $1.",
"description": "Points the user to Etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"Etherscan\" followed by an info icon that shows more info on hover."
@ -2317,7 +2370,7 @@
"message": "Kinakailangan ng mga URL ang naaangkop na HTTP/HTTPS prefix."
},
"urlExistsErrorMsg": {
"message": "Nasa kasalukuyang listahan ng mga network na ang URL"
"message": "Kasalukuyang ginagamit ng $1 network ang URL na ito."
},
"usePhishingDetection": {
"message": "Gumamit ng Pag-detect ng Phishing"
@ -2339,6 +2392,10 @@
"message": "I-verify ang token na ito sa $1",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"verifyThisUnconfirmedTokenOn": {
"message": "I-verify ang token na ito sa $1 at tiyaking ito ang token na gusto mong i-trade.",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"viewAccount": {
"message": "Tingnan ang Account"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "Twoja przeglądarka nie jest obsługiwana..."
},
"builtInCalifornia": {
"message": "MetaMask został zaprojektowany i stworzony w Kaliforni."
},
"buyWithWyre": {
"message": "Kup ETH poprzez Wyre"
},
@ -748,9 +745,6 @@
"recents": {
"message": "Ostatnie"
},
"recipientAddress": {
"message": "Adres odbiorcy"
},
"recipientAddressPlaceholder": {
"message": "Szukaj, adres publiczny (0x) lub ENS"
},

@ -43,9 +43,6 @@
"blockiesIdenticon": {
"message": "Usar Blockies Identicon"
},
"builtInCalifornia": {
"message": "MetaMask é desenhada e construída na California."
},
"cancel": {
"message": "Cancelar"
},
@ -282,9 +279,6 @@
"readdToken": {
"message": "Pode adicionar este token de novo clicando na opção “Adicionar token” no menu de opções da sua conta."
},
"recipientAddress": {
"message": "Endereço do Destinatário"
},
"reject": {
"message": "Rejeitar"
},

@ -52,6 +52,10 @@
"addContact": {
"message": "Adicionar contato"
},
"addCustomTokenByContractAddress": {
"message": "Não conseguiu encontrar um token? Cole o endereço para adicionar manualmente qualquer token. Os endereços de contato do token podem ser encontrados em $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Isso permitirá esta rede ser usada dentro do MetaMask."
},
@ -249,12 +253,9 @@
"browserNotSupported": {
"message": "Seu navegador não é compatível..."
},
"builContactList": {
"buildContactList": {
"message": "Crie sua lista de contatos"
},
"builtInCalifornia": {
"message": "O MetaMask é projetado e construído na Califórnia."
},
"buy": {
"message": "Comprar"
},
@ -285,6 +286,9 @@
"chainIdDefinition": {
"message": "O ID da chain usado para assinar transações para essa rede."
},
"chainIdExistsErrorMsg": {
"message": "O ID da chain é usado no momento pela rede $1."
},
"chromeRequiredForHardwareWallets": {
"message": "Você precisa usar MetaMask no Google Chrome para se conectar com sua carteira de hardware."
},
@ -410,6 +414,9 @@
"continueToWyre": {
"message": "Continuar para o Wyre"
},
"contract": {
"message": "Contrato"
},
"contractAddressError": {
"message": "Você está enviando tokens ao endereço de contrato do token. Isso pode resultar na perda destes tokens."
},
@ -626,6 +633,10 @@
"endOfFlowMessage6": {
"message": "Se você precisar fazer backup da sua Frase de recuperação secreta novamente, encontre-a em Configurações -> Segurança."
},
"endOfFlowMessage7": {
"message": "Se você tiver alguma pergunta ou vir algo suspeito, entre em contato com o atendimento ao cliente em $1.",
"description": "$1 is a clickable link with text defined by the 'here' key. The link will open to a form where users can file support tickets."
},
"endOfFlowMessage8": {
"message": "O MetaMask não pode recuperar sua Frase de recuperação secreta."
},
@ -885,6 +896,16 @@
"importAccountSeedPhrase": {
"message": "Importe uma conta com a Frase de recuperação secreta"
},
"importAccountText": {
"message": "ou $1",
"description": "$1 represents the text from `importAccountLinkText` as a link"
},
"importTokenQuestion": {
"message": "Importar token?"
},
"importTokenWarning": {
"message": "Qualquer pessoa pode criar um token com um nome, incluindo versões falsas de tokens existentes. Adicione e negocie, assumindo o risco sozinho!"
},
"importWallet": {
"message": "Importar carteira"
},
@ -961,6 +982,12 @@
"invalidSeedPhrase": {
"message": "Frase de recuperação secreta inválida"
},
"ipfsGateway": {
"message": "Gateway IPFS"
},
"ipfsGatewayDescription": {
"message": "Informe o URL do gateway de CID do IPFS para usar com resolução de conteúdo de ENS."
},
"jsonFile": {
"message": "Arquivo JSON",
"description": "format for importing an account"
@ -1104,7 +1131,7 @@
"message": "Informe sua senha para confirmar que é você mesmo!"
},
"mustSelectOne": {
"message": "Selecione pelo menos 1 token."
"message": "Selecione pelo menos 1 token."
},
"myAccounts": {
"message": "Minhas contas"
@ -1281,8 +1308,24 @@
"message": "A sua \"Frase Semente\" agora é chamada de sua \"Frase Secreta de Recuperação.\"",
"description": "Description of a notification in the 'See What's New' popup. Describes the seed phrase wording update."
},
"notifications6DescriptionOne": {
"message": "A partir do Chrome versão 91, a API que permitia nosso suporte ao Ledger (U2F) não é mais compatível com carteiras de hardware. O MetaMask implementou um novo suporte ao Ledger Live que permite continuar conectando o seu dispositivo Ledger device por meio do aplicativo de desktop Ledger Live.",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionThree": {
"message": "Ao interagir com sua conta do Ledger no MetaMask, uma nova aba será aberta e você deverá abrir o aplicativo Ledger Live. Quando o aplicativo for aberto, você precisará permitir uma conexão do WebSocket com sua conta do MetaMask. É isso!",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionTwo": {
"message": "Você pode habilitar o suporte do Ledger Live clicando em Configurações > Avançadas > Usar Ledger Live.",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6Title": {
"message": "Atualização de suporte do Ledger para usuários do Chrome",
"description": "Title for a notification in the 'See What's New' popup. Lets users know about the Ledger support update"
},
"ofTextNofM": {
"message": " de "
"message": "de"
},
"off": {
"message": "Desativado"
@ -1405,12 +1448,33 @@
"recents": {
"message": "Recentes"
},
"recipientAddress": {
"message": "Endereço do destinatário"
},
"recipientAddressPlaceholder": {
"message": "Busca, endereço público (0x) ou ENS"
},
"recoveryPhraseReminderBackupStart": {
"message": "Iniciar aqui"
},
"recoveryPhraseReminderConfirm": {
"message": "Entendi"
},
"recoveryPhraseReminderHasBackedUp": {
"message": "Mantenha sempre o sigilo e proteja a sua Frase de Recuperação Secreta."
},
"recoveryPhraseReminderHasNotBackedUp": {
"message": "Precisa fazer backup da sua Frase de recuperação Secreta novamente?"
},
"recoveryPhraseReminderItemOne": {
"message": "Nunca compartilhe a sua Frase de recuperação secreta com ninguém"
},
"recoveryPhraseReminderItemTwo": {
"message": "A equipe do MetaMask jamais pedirá sua Frase de recuperação secreta."
},
"recoveryPhraseReminderSubText": {
"message": "Sua Frase de recuperação secreta controla todas as suas contas."
},
"recoveryPhraseReminderTitle": {
"message": "Proteja seu dinheiro"
},
"reject": {
"message": "Rejeitar"
},
@ -1459,6 +1523,16 @@
"restoreAccountWithSeed": {
"message": "Restaure sua conta com a Frase de recuperação secreta"
},
"restoreWalletPreferences": {
"message": "Encontramos um backup dos seus dados de $1. Gostaria de restaurar as preferências da sua carteira?",
"description": "$1 is the date at which the data was backed up"
},
"retryTransaction": {
"message": "Refazer transação"
},
"reusedTokenNameWarning": {
"message": "O token aqui reutiliza um símbolo de outro token que você observa; isso pode causar confusões ou induzir ao erro."
},
"revealSeedWords": {
"message": "Revelar Frase de recuperação secreta"
},
@ -1528,6 +1602,42 @@
"securitySettingsDescription": {
"message": "Configurações de privacidade e Frase de recuperação secreta"
},
"seedPhraseIntroSidebarBulletFour": {
"message": "Anote e guarde em vários locais secretos."
},
"seedPhraseIntroSidebarBulletOne": {
"message": "Salve em um gerenciador de senhas"
},
"seedPhraseIntroSidebarBulletThree": {
"message": "Guarde dentro de um cofre."
},
"seedPhraseIntroSidebarBulletTwo": {
"message": "Guarde em um cofre-forte bancário."
},
"seedPhraseIntroSidebarCopyOne": {
"message": "A sua frase de recuperação é a “chave-mestra” para sua carteira e seus fundos."
},
"seedPhraseIntroSidebarCopyThree": {
"message": "Caso alguém lhe peça a sua frase de recuperação, essa pessoa provavelmente está tentando dar um golpe em você."
},
"seedPhraseIntroSidebarCopyTwo": {
"message": "Jamais compartilhe a sua frase de recuperação, mesmo com o MetaMask!"
},
"seedPhraseIntroSidebarTitleOne": {
"message": "O que é uma frase de recuperação?"
},
"seedPhraseIntroSidebarTitleThree": {
"message": "Devo compartilhar minha frase de recuperação?"
},
"seedPhraseIntroSidebarTitleTwo": {
"message": "Como salvo minha frase de recuperação?"
},
"seedPhraseIntroTitle": {
"message": "Proteger sua carteira"
},
"seedPhraseIntroTitleCopy": {
"message": "Antes de iniciar, assista esse vídeo curto para aprender sobre sua frase de recuperação e sobre como manter sua carteira segura."
},
"seedPhrasePlaceholder": {
"message": "Separe cada palavra com um único espaço"
},
@ -1990,6 +2100,9 @@
"message": "Swap $1 para $2",
"description": "Used in the transaction display list to describe a swap. $1 and $2 are the symbols of tokens in involved in a swap."
},
"swapTokenVerificationAddedManually": {
"message": "Este token foi adicionado manualmente."
},
"swapTokenVerificationMessage": {
"message": "Sempre confirme o endereço do token em $1.",
"description": "Points the user to Etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"Etherscan\" followed by an info icon that shows more info on hover."
@ -2257,7 +2370,7 @@
"message": "Os URLs precisam do prefixo HTTP/HTTPS adequado."
},
"urlExistsErrorMsg": {
"message": "O URL já está presente na lista de redes existente"
"message": "O ID da chain é usado no momento pela rede $1."
},
"usePhishingDetection": {
"message": "Usar detecção de phishing"
@ -2279,6 +2392,10 @@
"message": "Verificar este token em $1",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"verifyThisUnconfirmedTokenOn": {
"message": "Verifique este token em $1 garanta que seja o token que você deseja negociar.",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"viewAccount": {
"message": "Exibir conta"
},
@ -2312,6 +2429,10 @@
"walletSeedRestore": {
"message": "Frase de recuperação secreta da carteira"
},
"web3ShimUsageNotification": {
"message": "Percebemos que o site atual tentou usar a API window.web3 removida. Se o site parecer estar corrompido, clique em $1 para obter mais informações.",
"description": "$1 is a clickable link."
},
"welcome": {
"message": "Bem-vindo(a) ao MetaMask"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "Browserul dvs. nu este compatibil..."
},
"builtInCalifornia": {
"message": "MetaMask este concepută și creată în California."
},
"buyWithWyre": {
"message": "Cumpărați ETH cu Wyre"
},
@ -741,9 +738,6 @@
"recents": {
"message": "Recente"
},
"recipientAddress": {
"message": "Adresă destinatar"
},
"recipientAddressPlaceholder": {
"message": "Căutare, adresa publică (0x) sau ENS"
},

@ -52,6 +52,10 @@
"addContact": {
"message": "Добавить контакт"
},
"addCustomTokenByContractAddress": {
"message": "Невозможно найти токен? Вы можете вручную добавить любой токен, вставив его адрес. Контактные адреса токена можно найти на $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Это позволит использовать ее в MetaMask."
},
@ -249,12 +253,9 @@
"browserNotSupported": {
"message": "Ваш браузер не поддерживается..."
},
"builContactList": {
"buildContactList": {
"message": "Создайте список контактов"
},
"builtInCalifornia": {
"message": "MetaMask разработан и построен в Калифорнии."
},
"buy": {
"message": "Купить"
},
@ -280,11 +281,14 @@
"message": "Отменено"
},
"chainId": {
"message": "Идентификатор цепи"
"message": "Идентификатор цепочки"
},
"chainIdDefinition": {
"message": "Идентификатор цепочки, используемый для подписания транзакций для этой сети."
},
"chainIdExistsErrorMsg": {
"message": "Этот идентификатор цепочки в настоящее время используется сетью $1."
},
"chromeRequiredForHardwareWallets": {
"message": "Вам необходимо использовать MetaMask в Google Chrome, чтобы подключиться к аппаратному кошельку."
},
@ -410,6 +414,9 @@
"continueToWyre": {
"message": "Продолжить к Wyre"
},
"contract": {
"message": "Контракт"
},
"contractAddressError": {
"message": "Вы отправляете токены на адрес контракта токена. Это может привести к потере токенов."
},
@ -626,6 +633,10 @@
"endOfFlowMessage6": {
"message": "Если вам нужно снова создать резервную копию секретной фразы восстановления, вы можете найти ее в Настройки -> Безопасность."
},
"endOfFlowMessage7": {
"message": "Если у вас возникнут вопросы или вы увидите что-то подозрительное, обратитесь в службу поддержки $1.",
"description": "$1 is a clickable link with text defined by the 'here' key. The link will open to a form where users can file support tickets."
},
"endOfFlowMessage8": {
"message": "Просто помните, что MetaMask не может восстановить секретную фразу восстановления."
},
@ -885,6 +896,16 @@
"importAccountSeedPhrase": {
"message": "Импортировать счет с секретной фразой восстановления"
},
"importAccountText": {
"message": "или $1",
"description": "$1 represents the text from `importAccountLinkText` as a link"
},
"importTokenQuestion": {
"message": "Импортировать токен?"
},
"importTokenWarning": {
"message": "Кто угодно может создать токен с любым именем, включая поддельные версии существующих токенов. Добавляйте и торгуйте на свой страх и риск!"
},
"importWallet": {
"message": "Импортировать кошелек"
},
@ -1287,6 +1308,22 @@
"message": "Исходная фраза теперь называется секретной фразой восстановления.",
"description": "Description of a notification in the 'See What's New' popup. Describes the seed phrase wording update."
},
"notifications6DescriptionOne": {
"message": "Начиная с Chrome версии 91, API, обеспечивающий поддержку нашего Ledger (U2F), аппаратные кошельки больше не поддерживаются. MetaMask реализовала новую поддержку Ledger Live, которая позволяет продолжать подключаться к устройству Ledger через настольное приложение Ledger Live.",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionThree": {
"message": "При взаимодействии с вашим счетом Ledger в MetaMask откроется новая вкладка, и вам будет предложено открыть приложение Ledger Live. Когда приложение откроется, вам будет предложено разрешить WebSocket-соединение с вашим счетом MetaMask. Вот и все!",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionTwo": {
"message": "Вы можете включить поддержку Ledger Live, нажав «Настройки» > «Дополнительно» > «Использовать Ledger Live».",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6Title": {
"message": "Обновление поддержки Ledger для пользователей Chrome",
"description": "Title for a notification in the 'See What's New' popup. Lets users know about the Ledger support update"
},
"ofTextNofM": {
"message": "из"
},
@ -1411,12 +1448,33 @@
"recents": {
"message": "Недавние"
},
"recipientAddress": {
"message": "Адрес получателя"
},
"recipientAddressPlaceholder": {
"message": "Поиск, публичный адрес (0x) или ENS"
},
"recoveryPhraseReminderBackupStart": {
"message": "Начать здесь"
},
"recoveryPhraseReminderConfirm": {
"message": "Понятно"
},
"recoveryPhraseReminderHasBackedUp": {
"message": "Всегда храните свою секретную фразу восстановления в надежном и секретном месте"
},
"recoveryPhraseReminderHasNotBackedUp": {
"message": "Нужно снова сделать резервную копию секретной фразы восстановления?"
},
"recoveryPhraseReminderItemOne": {
"message": "Никогда никому не сообщайте свою секретную фразу восстановления"
},
"recoveryPhraseReminderItemTwo": {
"message": "Команда MetaMask никогда неожиданно не запросит вашу секретную фразу восстановления"
},
"recoveryPhraseReminderSubText": {
"message": "Ваша секретная фраза восстановления контролирует все ваши счета."
},
"recoveryPhraseReminderTitle": {
"message": "Защитите свои активы"
},
"reject": {
"message": "Отклонить"
},
@ -1476,7 +1534,7 @@
"message": "Токен здесь повторно использует символ из другого токена, который вы смотрите, это может запутать или ввести в заблуждение."
},
"revealSeedWords": {
"message": "Показать секретную фразу восстановления"
"message": "Раскрыть секретную фразу восстановления"
},
"revealSeedWordsDescription": {
"message": "Если вы меняете браузер или переходите на другой компьютер, вам понадобится эта секретная фраза восстановления для доступа к своим счетам. Сохраните ее в безопасном секретном месте."
@ -1544,6 +1602,42 @@
"securitySettingsDescription": {
"message": "Настройки конфиденциальности и секретная фраза восстановления кошелька"
},
"seedPhraseIntroSidebarBulletFour": {
"message": "Запишите и храните в нескольких секретных местах."
},
"seedPhraseIntroSidebarBulletOne": {
"message": "В диспетчере паролей."
},
"seedPhraseIntroSidebarBulletThree": {
"message": "В банковской ячейке."
},
"seedPhraseIntroSidebarBulletTwo": {
"message": "В банковском сейфе."
},
"seedPhraseIntroSidebarCopyOne": {
"message": "Фраза восстановления — это главный ключ к кошельку и средствам в нем."
},
"seedPhraseIntroSidebarCopyThree": {
"message": "Если кто-нибудь интересуется вашей фразой восстановления, этот человек, скорее всего, пытается вас обмануть."
},
"seedPhraseIntroSidebarCopyTwo": {
"message": "Не сообщайте свою фразу восстановления никому, даже сотрудникам MetaMask."
},
"seedPhraseIntroSidebarTitleOne": {
"message": "Что такое фраза восстановления?"
},
"seedPhraseIntroSidebarTitleThree": {
"message": "Можно ли сообщать кому-либо свою фразу восстановления?"
},
"seedPhraseIntroSidebarTitleTwo": {
"message": "Как хранить фразу восстановления?"
},
"seedPhraseIntroTitle": {
"message": "Защитите свой кошелек"
},
"seedPhraseIntroTitleCopy": {
"message": "Прежде чем приступить к работе, посмотрите это короткое видео о том, что такое фраза восстановления и как обезопасить кошелек."
},
"seedPhrasePlaceholder": {
"message": "Отделяйте каждое слово одним пробелом"
},
@ -2006,6 +2100,9 @@
"message": "Своп $1 на $2",
"description": "Used in the transaction display list to describe a swap. $1 and $2 are the symbols of tokens in involved in a swap."
},
"swapTokenVerificationAddedManually": {
"message": "Этот токен был добавлен вручную."
},
"swapTokenVerificationMessage": {
"message": "Всегда проверяйте адрес токена на $1.",
"description": "Points the user to Etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"Etherscan\" followed by an info icon that shows more info on hover."
@ -2273,7 +2370,7 @@
"message": "Для URL требуется соответствующий префикс HTTP/HTTPS."
},
"urlExistsErrorMsg": {
"message": "URL уже присутствует в имеющемся списке сетей"
"message": "Это URL в настоящее время используется сетью $1."
},
"usePhishingDetection": {
"message": "Использовать обнаружение фишинга"
@ -2295,6 +2392,10 @@
"message": "Проверить этот токен на $1",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"verifyThisUnconfirmedTokenOn": {
"message": "Проверьте этот токен на $1 и убедитесь, что это тот токен, которым вы хотите торговать.",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"viewAccount": {
"message": "Посмотреть счет"
},
@ -2328,6 +2429,10 @@
"walletSeedRestore": {
"message": "Секретная фраза восстановления кошелька"
},
"web3ShimUsageNotification": {
"message": "Мы заметили, что текущий веб-сайт пытался использовать удаленный API window.web3. Если сайт не работает, нажмите $1 для получения дополнительной информации.",
"description": "$1 is a clickable link."
},
"welcome": {
"message": "Добро пожаловать в MetaMask"
},

@ -137,9 +137,6 @@
"browserNotSupported": {
"message": "Váš prehliadač nie je podporovaný..."
},
"builtInCalifornia": {
"message": "MetaMask je navržen a vytvořen v Kalifornii."
},
"buyWithWyre": {
"message": "Kúpte ETH s Wyre"
},
@ -723,9 +720,6 @@
"recents": {
"message": "Posledné"
},
"recipientAddress": {
"message": "Adresa příjemce"
},
"recipientAddressPlaceholder": {
"message": "Vyhľadávať verejnú adresu (0x) alebo ENS"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "Vaš brskalnik ni podptrt ..."
},
"builtInCalifornia": {
"message": "MetaMask je zasnovan in ustvarjen v Kaliforniji."
},
"buyWithWyre": {
"message": "Kupi ETH z Wyre"
},
@ -742,9 +739,6 @@
"recents": {
"message": "Nedavno"
},
"recipientAddress": {
"message": "Prejemnikov naslov"
},
"recipientAddressPlaceholder": {
"message": "Iskanje, javni naslov (0x) ali ENS"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "Vaš pregledač nije podržan..."
},
"builtInCalifornia": {
"message": "MetaMask je dizajniran i izgrađen u Kaliforniji."
},
"buyWithWyre": {
"message": "Kupite ETH preko servisa Wyre"
},
@ -745,9 +742,6 @@
"recents": {
"message": "Skorašnje"
},
"recipientAddress": {
"message": "Adresa primaoca"
},
"recipientAddressPlaceholder": {
"message": "Pretraga, javna adresa (0x) ili ENS"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "Din webbläsare stöds inte..."
},
"builtInCalifornia": {
"message": "MetaMask är skapat och utformat i Kalifornien."
},
"buyWithWyre": {
"message": "Köp ETH med Wyre"
},
@ -738,9 +735,6 @@
"recents": {
"message": "Senaste"
},
"recipientAddress": {
"message": "Mottagaradress"
},
"recipientAddressPlaceholder": {
"message": "Sök, allmän adress (0x) eller ENS"
},

@ -140,9 +140,6 @@
"browserNotSupported": {
"message": "Kivinjari chaku hakiwezeshwi..."
},
"builtInCalifornia": {
"message": "MetaMask imeundwa na kutengenezwa California."
},
"buyWithWyre": {
"message": "Nunua ETH kwa kutumia Wyre"
},
@ -732,9 +729,6 @@
"recents": {
"message": "Za hivi karibuni"
},
"recipientAddress": {
"message": "Anwani ya Mpokeaji"
},
"recipientAddressPlaceholder": {
"message": "Tafuta, anwani za umma (0x), au ENS"
},

@ -55,9 +55,6 @@
"blockiesIdenticon": {
"message": "பி ஐடி பயன"
},
"builtInCalifornia": {
"message": "மடமஸ வடிவமகபபட கலிிி கடடபபடளத."
},
"cancel": {
"message": "ரத"
},
@ -372,9 +369,6 @@
"readdToken": {
"message": "உஙகள கணகிபஙகளி \"டகன\" எனபதனலமகள எதிலதி இநத டகனகல."
},
"recipientAddress": {
"message": "பநரகவரி"
},
"reject": {
"message": "நிகரி"
},

@ -49,9 +49,6 @@
"blockiesIdenticon": {
"message": "ใชงาน Blockies Identicon"
},
"builtInCalifornia": {
"message": "MetaMask ออกแบบและพฒนาทแคลฟอรเนย"
},
"cancel": {
"message": "ยกเลก"
},
@ -375,9 +372,6 @@
"readdToken": {
"message": "คณสามารถเพมโทเคนนในอนาคตไดโดยไปท “เพมโทเคน” ในเมนวเลอกบญชของคณ"
},
"recipientAddress": {
"message": "แอดแดรสผบ"
},
"reject": {
"message": "ปฏเสธ"
},

@ -211,9 +211,6 @@
"browserNotSupported": {
"message": "Hindi sinusuportahan ang iyong Browser..."
},
"builtInCalifornia": {
"message": "Ang MetaMask ay idinisenyo at binuo sa California."
},
"buy": {
"message": "Bilhin"
},
@ -1192,9 +1189,6 @@
"recents": {
"message": "Mga Kamakailan"
},
"recipientAddress": {
"message": "Address ng Tatanggap"
},
"recipientAddressPlaceholder": {
"message": "Maghanap, pampublikong address (0x), o ENS"
},

@ -46,9 +46,6 @@
"blockiesIdenticon": {
"message": "Blockies Identicon kullan"
},
"builtInCalifornia": {
"message": "MetaMask California'da tasarlandı ve yaratıldı"
},
"cancel": {
"message": "Vazgeç"
},
@ -324,9 +321,6 @@
"readdToken": {
"message": "Gelecekte Bu jetonu hesap seçenekleri menüsünde “Jeton ekle”'ye giderek geri ekleyebilirsiniz."
},
"recipientAddress": {
"message": "Alıcı adresi"
},
"reject": {
"message": "Reddetmek"
},

@ -143,9 +143,6 @@
"browserNotSupported": {
"message": "Ваш браузер не підтримується..."
},
"builtInCalifornia": {
"message": "MetaMask розроблено й створено в Каліфорнії."
},
"buyWithWyre": {
"message": "Купити ETH через Wyre"
},
@ -754,9 +751,6 @@
"recents": {
"message": "Останні"
},
"recipientAddress": {
"message": "Адреса отримувача"
},
"recipientAddressPlaceholder": {
"message": "Пошук, публічна адреса (0x), або ENS"
},

@ -52,6 +52,10 @@
"addContact": {
"message": "Thêm người liên hệ"
},
"addCustomTokenByContractAddress": {
"message": "Bạn không tìm thấy token? Bạn có thể dán địa chỉ của bất kỳ token nào để thêm token đó theo cách thủ công. Bạn có thể tìm thấy địa chỉ hợp đồng token trên $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Thao tác này sẽ cho phép sử dụng mạng này trong MetaMask."
},
@ -249,12 +253,9 @@
"browserNotSupported": {
"message": "Trình duyệt của bạn không được hỗ trợ..."
},
"builContactList": {
"buildContactList": {
"message": "Xây dựng danh sách liên hệ của bạn"
},
"builtInCalifornia": {
"message": "MetaMask được thiết kế và phát triển tại California."
},
"buy": {
"message": "Mua"
},
@ -285,6 +286,9 @@
"chainIdDefinition": {
"message": "Mã chuỗi được dùng để ký các giao dịch cho mạng này."
},
"chainIdExistsErrorMsg": {
"message": "Mạng $1 hiện đang sử dụng mã chuỗi này."
},
"chromeRequiredForHardwareWallets": {
"message": "Bạn cần sử dụng MetaMask trên Google Chrome để kết nối với Ví cứng của bạn."
},
@ -410,6 +414,9 @@
"continueToWyre": {
"message": "Tiếp tục chuyển đến Wyre"
},
"contract": {
"message": "Hợp đồng"
},
"contractAddressError": {
"message": "Bạn đang gửi token đến địa chỉ hợp đồng của token. Điều này có thể khiến bạn bị mất những token này."
},
@ -624,7 +631,11 @@
"message": "Hãy cẩn thận với hoạt động lừa đảo! MetaMask sẽ không bao giờ tự ý hỏi Cụm mật khẩu khôi phục bí mật của bạn."
},
"endOfFlowMessage6": {
"message": "Nếu bạn cần sao lưu lại Cụm mật khẩu khôi phục bí mật, bạn có thể tìm thấy chức năng này trong Cài đặt -> Bảo mật."
"message": "Nếu bạn cần sao lưu lại Cụm mật khẩu khôi phục bí mật, bạn có thể tìm thấy chức năng này trong phần Cài đặt -> Bảo mật."
},
"endOfFlowMessage7": {
"message": "Nếu bạn có thắc mắc hoặc thấy điều gì đó đáng ngờ, hãy liên hệ với bộ phận hỗ trợ của chúng tôi $1.",
"description": "$1 is a clickable link with text defined by the 'here' key. The link will open to a form where users can file support tickets."
},
"endOfFlowMessage8": {
"message": "MetaMask không thể khôi phục Cụm mật khẩu khôi phục bí mật của bạn."
@ -885,6 +896,16 @@
"importAccountSeedPhrase": {
"message": "Nhập một tài khoản bằng Cụm mật khẩu khôi phục bí mật"
},
"importAccountText": {
"message": "hoặc $1",
"description": "$1 represents the text from `importAccountLinkText` as a link"
},
"importTokenQuestion": {
"message": "Bạn muốn nhập token?"
},
"importTokenWarning": {
"message": "Bất kỳ ai cũng tạo được token bằng bất kỳ tên nào, kể cả phiên bản giả của token hiện có. Bạn tự chịu rủi ro khi thêm và giao dịch!"
},
"importWallet": {
"message": "Nhập ví"
},
@ -1287,6 +1308,22 @@
"message": "Từ giờ, \"Cụm mật khẩu gốc\" sẽ được gọi là \"Cụm mật khẩu khôi phục bí mật.\"",
"description": "Description of a notification in the 'See What's New' popup. Describes the seed phrase wording update."
},
"notifications6DescriptionOne": {
"message": "Kể từ phiên bản Chrome 91, API từng cho phép hỗ trợ Ledger (U2F) của chúng tôi không còn hỗ trợ ví cứng nữa. MetaMask đã triển khai một tính năng hỗ trợ Ledger Live mới cho phép bạn tiếp tục kết nối với thiết bị Ledger của mình thông qua ứng dụng Ledger Live trên máy tính.",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionThree": {
"message": "Khi tương tác với tài khoản Ledger của bạn trong MetaMask, một tab mới sẽ mở ra và bạn sẽ được yêu cầu mở ứng dụng Ledger Live. Khi ứng dụng này mở ra, bạn sẽ được yêu cầu cho phép kết nối WebSocket với tài khoản MetaMask của mình. Đơn giản vậy thôi!",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6DescriptionTwo": {
"message": "Bạn có thể kích hoạt tính năng hỗ trợ Ledger Live bằng cách nhấp vào phần Cài đặt > Nâng cao > Sử dụng Ledger Live.",
"description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update."
},
"notifications6Title": {
"message": "Thông tin cập nhật về việc hỗ trợ Ledger cho người dùng Chrome",
"description": "Title for a notification in the 'See What's New' popup. Lets users know about the Ledger support update"
},
"ofTextNofM": {
"message": "trên"
},
@ -1411,12 +1448,33 @@
"recents": {
"message": "Gần đây"
},
"recipientAddress": {
"message": "Địa chỉ người nhận"
},
"recipientAddressPlaceholder": {
"message": "Tìm kiếm, địa chỉ công khai (0x) hoặc ENS"
},
"recoveryPhraseReminderBackupStart": {
"message": "Bắt đầu tại đây"
},
"recoveryPhraseReminderConfirm": {
"message": "Đã hiểu"
},
"recoveryPhraseReminderHasBackedUp": {
"message": "Luôn lưu giữ Cụm mật khẩu khôi phục bí mật ở nơi an toàn và bí mật"
},
"recoveryPhraseReminderHasNotBackedUp": {
"message": "Bạn cần sao lưu lại Cụm mật khẩu khôi phục bí mật?"
},
"recoveryPhraseReminderItemOne": {
"message": "Tuyệt đối không cho ai biết Cụm mật khẩu khôi phục bí mật"
},
"recoveryPhraseReminderItemTwo": {
"message": "Nhóm MetaMask sẽ không bao giờ hỏi Cụm mật khẩu khôi phục bí mật của bạn"
},
"recoveryPhraseReminderSubText": {
"message": "Cụm mật khẩu khôi phục bí mật sẽ kiểm soát mọi thứ trong tài khoản của bạn."
},
"recoveryPhraseReminderTitle": {
"message": "Bảo vệ tiền của bạn"
},
"reject": {
"message": "Từ chối"
},
@ -1442,7 +1500,7 @@
"message": "Xóa tài khoản"
},
"removeAccountDescription": {
"message": "Tài khoản này sẽ được xóa khỏi ví của bạn. Xin đảm bảo rằng bạn có Cụm mật khẩu khôi phục bí mật ban đầu hoặc khóa riêng tư cho tài khoản được nhập trước khi tiếp tục. Bạn có thể nhập hoặc tạo lại tài khoản từ trình đơn tài khoản thả xuống. "
"message": "Tài khoản này sẽ được xóa khỏi ví của bạn. Hãy đảm bảo rằng bạn có Cụm mật khẩu khôi phục bí mật ban đầu hoặc khóa riêng tư cho tài khoản được nhập trước khi tiếp tục. Bạn có thể nhập hoặc tạo lại tài khoản từ trình đơn tài khoản thả xuống. "
},
"requestsAwaitingAcknowledgement": {
"message": "yêu cầu đang chờ xác nhận"
@ -1476,10 +1534,10 @@
"message": "Một token trong đây sử dụng lại ký hiệu của một token khác mà bạn thấy, điều này có thể gây nhầm lẫn hoặc mang tính lừa dối."
},
"revealSeedWords": {
"message": "Hiện cụm mật khẩu khôi phục bí mật"
"message": "Hiện Cụm mật khẩu khôi phục bí mật"
},
"revealSeedWordsDescription": {
"message": "Nếu thay đổi trình duyệt hoặc chuyển máy tính, bạn sẽ cần Cụm mật khẩu khôi phục bí mật này để truy cập tài khoản của mình. Hãy lưu cụm mật khẩu gốc này ở nơi an toàn và bí mật."
"message": "Nếu thay đổi trình duyệt hoặc chuyển máy tính, bạn sẽ cần Cụm mật khẩu khôi phục bí mật này để truy cập tài khoản của mình. Hãy lưu Cụm mật khẩu khôi phục bí mật này ở nơi an toàn và bí mật."
},
"revealSeedWordsTitle": {
"message": "Cụm mật khẩu khôi phục bí mật"
@ -1544,6 +1602,42 @@
"securitySettingsDescription": {
"message": "Các cài đặt quyền riêng tư và Cụm mật khẩu khôi phục bí mật của ví"
},
"seedPhraseIntroSidebarBulletFour": {
"message": "Viết ra và cất ở nhiều nơi bí mật."
},
"seedPhraseIntroSidebarBulletOne": {
"message": "Lưu trong một trình quản lý mật khẩu"
},
"seedPhraseIntroSidebarBulletThree": {
"message": "Lưu giữ trong hộp ký gửi an toàn."
},
"seedPhraseIntroSidebarBulletTwo": {
"message": "Lưu giữ trong két an toàn."
},
"seedPhraseIntroSidebarCopyOne": {
"message": "Cụm mật khẩu khôi phục bí mật là “chìa khóa chính” để truy cập ví và số tiền của bạn."
},
"seedPhraseIntroSidebarCopyThree": {
"message": "Nếu ai đó hỏi bạn cụm mật khẩu khôi phục bí mật, thì họ đang cố gắng lừa đảo bạn."
},
"seedPhraseIntroSidebarCopyTwo": {
"message": "Đừng bao giờ cho ai biết cụm mật khẩu khôi phục bí mật, kể cả MetaMask!"
},
"seedPhraseIntroSidebarTitleOne": {
"message": "Cụm mật khẩu khôi phục là gì?"
},
"seedPhraseIntroSidebarTitleThree": {
"message": "Tôi có nên cho ai biết cụm mật khẩu khôi phục bí mật của mình không?"
},
"seedPhraseIntroSidebarTitleTwo": {
"message": "Tôi lưu cụm mật khẩu khôi phục của mình bằng cách nào?"
},
"seedPhraseIntroTitle": {
"message": "Bảo mật cho ví của bạn"
},
"seedPhraseIntroTitleCopy": {
"message": "Trước khi bắt đầu, hãy xem video ngắn này để tìm hiểu thêm về cụm mật khẩu khôi phục bí mật của bạn và cách bảo vệ ví của bạn."
},
"seedPhrasePlaceholder": {
"message": "Phân tách mỗi từ bằng một dấu cách"
},
@ -1844,7 +1938,7 @@
"message": "Đang hoàn tất..."
},
"swapFromTo": {
"message": "Hoán đổi $1 sang $2",
"message": "Giao dịch hoán đổi $1 sang $2",
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
},
"swapGasFeesSplit": {
@ -2006,6 +2100,9 @@
"message": "Hoán đổi $1 sang $2",
"description": "Used in the transaction display list to describe a swap. $1 and $2 are the symbols of tokens in involved in a swap."
},
"swapTokenVerificationAddedManually": {
"message": "Token này đã được thêm theo cách thủ công."
},
"swapTokenVerificationMessage": {
"message": "Luôn xác nhận địa chỉ token trên $1.",
"description": "Points the user to Etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"Etherscan\" followed by an info icon that shows more info on hover."
@ -2273,7 +2370,7 @@
"message": "URL phải có tiền tố HTTP/HTTPS phù hợp."
},
"urlExistsErrorMsg": {
"message": "URL đã có trong danh sách mạng hiện tại"
"message": "Mạng $1 hiện đang sử dụng URL này."
},
"usePhishingDetection": {
"message": "Sử dụng tính năng Phát hiện lừa đảo"
@ -2295,6 +2392,10 @@
"message": "Xác minh token này trên $1",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"verifyThisUnconfirmedTokenOn": {
"message": "Hãy xác minh token này trên $1 và đảm bảo đây là token bạn muốn giao dịch.",
"description": "Points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"viewAccount": {
"message": "Xem tài khoản"
},
@ -2328,6 +2429,10 @@
"walletSeedRestore": {
"message": "Cụm mật khẩu khôi phục bí mật của ví"
},
"web3ShimUsageNotification": {
"message": "Chúng tôi nhận thấy rằng trang web hiện tại đã cố dùng API window.web3 đã bị xóa. Nếu trang web có vẻ như đã bị lỗi, vui lòng nhấp vào $1 để biết thêm thông tin.",
"description": "$1 is a clickable link."
},
"welcome": {
"message": "Chào mừng bạn đến với MetaMask"
},

@ -217,9 +217,6 @@
"browserNotSupported": {
"message": "您的浏览器不支持该功能……"
},
"builtInCalifornia": {
"message": "MetaMask在加利福尼亚设计和制造。"
},
"buy": {
"message": "购买"
},
@ -1195,9 +1192,6 @@
"recents": {
"message": "最近记录"
},
"recipientAddress": {
"message": "接收地址"
},
"recipientAddressPlaceholder": {
"message": "查找、公用地址 (0x) 或 ENS"
},

@ -149,9 +149,6 @@
"browserNotSupported": {
"message": "您的瀏覽器尚未支援..."
},
"builtInCalifornia": {
"message": "MetaMask 是在加州設計製造"
},
"buy": {
"message": "買"
},
@ -751,9 +748,6 @@
"recents": {
"message": "最近"
},
"recipientAddress": {
"message": "接收位址"
},
"recipientAddressPlaceholder": {
"message": "搜尋,公開地址 (0x),或 ENS"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 2066.8 800" style="enable-background:new 0 0 2066.8 800;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:#2970E2;}
.st2{fill:#D1D9E6;}
.st3{fill:#FFFFFF;}
.st4{fill:url(#SVGID_2_);}
</style>
<g id="transparent_1_">
<g id="logo_4_">
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="119.932" y1="155.9459" x2="624.022" y2="547.843" gradientTransform="matrix(1 0 0 1 0 50)">
<stop offset="0.1304" style="stop-color:#3495F7"/>
<stop offset="0.3063" style="stop-color:#2B87F2"/>
<stop offset="0.6392" style="stop-color:#1461E5"/>
<stop offset="0.7232" style="stop-color:#0E57E1"/>
</linearGradient>
<path class="st0" d="M566.4,640.4H175.3c-22,0-39.8-17.8-39.8-39.8V201.5c0-22,17.8-39.8,39.8-39.8h391.2
c22,0,39.8,17.8,39.8,39.8v399.1C606.3,622.5,588.5,640.4,566.4,640.4z"/>
<path class="st1" d="M606.3,451.6v148.9c0,22-17.8,39.8-39.8,39.8H417.6L606.3,451.6z"/>
<g>
<g>
<g>
<path class="st2" d="M293.4,449.7l138.9-144.4c10.6-10.6,27.2-9.8,38.3,1.2l80.3,77.4c8.5,8.2,8.3,24.4,0.4,33.1
c-10.3,10.3-21.9,11-37.5,0.4l-58.2-63L309.3,493.5c-9.8,7.6-19.8,14-35.2,4.1c-5.3-7.1,5.2-21,1.1-25.1L293.4,449.7z"/>
</g>
<g>
<path class="st2" d="M287.1,498.2c-11.9-0.4-21.2-8.1-21.2-17.3l0.7-165.1c0-9.1,8.5-20.2,20.4-20.6
c15.8,0,22.7,12.8,22.7,22.3v163.8C309.8,490.8,299.5,498.5,287.1,498.2z"/>
</g>
<g>
<path class="st2" d="M449.1,502.7c-14.7,0-20.9-10.9-20.9-19.5V331.8c0-8.5,9.1-18.9,20.9-19.3c12.3-0.4,22.5,10,22.5,18.9
v149.7C471.6,491.7,464.3,502.7,449.1,502.7z"/>
</g>
</g>
<g>
<g>
<path class="st3" d="M282.1,500c-9.1-0.4-16.2-8.1-16.2-17.3V317.6c0-9.1,7.1-20.2,16.2-20.6c9.5-0.4,17.5,10.6,17.5,20.2v166
C299.6,492.7,291.7,500.4,282.1,500z"/>
</g>
<g>
<path class="st3" d="M444.5,501.7c-9.3,0-16.9-10.8-16.9-20.2V318.9c0-9.3,7.6-16.9,16.9-16.9c9.3,0,16.9,7.6,16.9,16.9v162.7
C461.3,490.8,453.8,501.7,444.5,501.7z"/>
</g>
<g>
<path class="st3" d="M282.8,498.4c-5.5,0-11-2.1-15.1-6.3l-76.5-76.7c-8.3-8.4-8.3-21.9,0.1-30.3c8.4-8.3,21.9-8.3,30.3,0.1
l61.6,62l147.4-140.3c8.3-7.9,21.3-7.9,29.6,0.1l80.2,77.4c8.5,8.2,8.7,21.7,0.6,30.3c-8.1,8.5-21.7,8.7-30.3,0.6l-65.5-63.1
L297.5,492.5C293.4,496.4,288.1,498.4,282.8,498.4z"/>
</g>
</g>
<path class="st3" d="M429.7,378.8l-9.3,8.8c0,0,2.6-14.7-4.9-7.6c-7.6,7.2,11.6-18.2,11.6-18.2l3.9,4.9L429.7,378.8z"/>
<path class="st3" d="M461.3,376.6c0,0,0.5-9,10.3,6.4v-12.1l-10.6-5.5L461.3,376.6z"/>
<path class="st3" d="M299.7,419.2c0,0,1.4,10,10.2-2.9v12.1l-10.6,5.5L299.7,419.2z"/>
</g>
</g>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="712.4824" y1="419.8029" x2="1947.4551" y2="419.8029">
<stop offset="0" style="stop-color:#3495F7"/>
<stop offset="0.4939" style="stop-color:#1461E5"/>
<stop offset="1" style="stop-color:#0E57E1"/>
</linearGradient>
<path class="st4" d="M809.3,337.3l2.1,0.7c0.3,5.7,0.5,16.4,0.5,32.1c0,1-1.5,1.4-4.5,1.4h-20.7c-1.9,0-2.9,1.5-2.9,4.5v122.8
c0,0.2-0.4,0.5-1.2,1.1c-0.8,0.6-1.3,0.8-1.7,0.8h-37.4l-1.7-1.4c-0.5-0.5-0.7-37.8-0.7-111.9v-14.8c-19.2,0-28.8-1.2-28.8-3.6
v-30.5c0-0.2,0.6-0.6,1.9-1.4h42.8c2.2,0.3,3.6,0.6,4,0.7c0.5,0,0.7-0.2,0.7-0.7H809.3z M868.1,336.9h65.9
c15.1,0,28.4,5.5,39.9,16.5c11.5,11,17.3,24.2,17.3,39.4c0,11.9-3.4,22.2-10.1,30.9c-6.7,8.7-15.8,15.5-27.3,20.2
c-0.5,0-0.7,0.4-0.7,1.2v0.2c0.2,0.2,0.2,0.3,0.2,0.5l40.7,49.7c0.5,0.8,1,1.7,1.4,2.6s0.8,1.6,1.1,2c0.2,0.4,0.4,0.7,0.4,0.8
c0,0.5-2.6,0.9-7.9,1.3c-5.2,0.4-8.7,0.6-10.5,0.6c-1.3,0-2.1-0.2-2.6-0.7h-25.5c-1.6-0.2-2.9-1-4-2.6l-33.1-39
c-0.2-0.2-0.5-0.6-1-1.2c-0.5-0.6-1-1.1-1.4-1.4c-0.5-0.3-1-0.5-1.4-0.5c-0.3,0-0.7,1-1.2,3.1v38.6c0,1.3-0.5,1.9-1.4,1.9h-32.4
c-2.9,0-4.8-0.6-5.7-1.9c-1-1.3-1.6-3.4-1.9-6.4c0-14.1,0.1-32,0.4-53.7c0.2-21.7,0.4-38,0.4-48.9c0-1.7-0.1-8.1-0.2-19l-0.5-16.4
C867.4,343.1,867.8,337.2,868.1,336.9z M948.3,392.3c0-4.4-1-8.3-3.1-11.7c-4-6.2-12.2-9.3-24.8-9.3c-2.2,0-5.6,0.2-10,0.5
l-2.1,2.4c0,2.1-0.1,5.3-0.2,9.6c-0.2,4.4-0.3,8.1-0.4,11.1c-0.1,3-0.1,5.2-0.1,6.4c0,10.6,1,16.6,2.9,17.9h2.6
c13.2-0.3,22.3-2.1,27.5-5.5C945.8,410.4,948.3,403.3,948.3,392.3z M1102.8,335h28.3c0.8,0,3.1,4.4,6.8,13.1
c3.7,8.7,5.6,13.6,5.6,14.5c3.3,8.7,21.3,53.1,54,133c0,2.2-1,3.6-2.9,4.2c-1.9,0.6-5.4,0.8-10.5,0.8l-12.1-0.2
c-8.1-0.2-14.2-0.2-18.3-0.2c-0.3-1-1-2.6-1.9-5c-1-2.4-1.7-4.2-2.3-5.5c-0.6-1.3-1.4-2.9-2.5-4.8c-1.1-1.9-2.3-3.6-3.6-5.2
c-1.1,0-3.3-0.2-6.7-0.6c-3.3-0.4-6.3-0.6-9-0.6l-29.8,0.5c-0.5,0-1.9-0.1-4.3-0.2c-2.4-0.2-3.7-0.2-3.8-0.2
c-2.9,0-5.3,3.7-7.4,11.2c-2.1,7.5-3.8,11.7-5.2,12.6c-4.3,0.2-9.6,0-15.8-0.4c-6.3-0.4-11.5-0.6-15.8-0.6c-6.7,0-10-1-10-2.9
c0-0.3,0-0.7,0.1-1.2c0.1-0.5,0.1-0.8,0.1-1l13.8-40c2.1-4.1,4.6-10.3,7.5-18.6c2.9-8.2,4.8-13.3,5.6-15.2
c3.2-8.2,8.6-22,16.3-41.2c7.7-19.2,13.5-34,17.5-44.5C1096.8,335.6,1098.8,335,1102.8,335z M1105.2,446.1h6.2
c11.4-0.3,17.1-1,17.1-1.9c0-2.9-2.5-10.1-7.4-21.7c-1.9-4.6-3-7.1-3.3-7.6l-3.1-7.4c-0.8,0-3.5,5.5-8,16.4
c-4.5,10.9-6.8,17.4-6.8,19.3C1099.9,445.1,1101.7,446.1,1105.2,446.1z M1388.2,426.3c0,0.3-0.1,1-0.2,2.1
c-0.2,1.1-0.3,2.2-0.5,3.3l-0.2,1.4c0,6.8,0.2,17.1,0.5,30.7c0.3,13.6,0.5,23.8,0.5,30.5c0,4.6-1,6.9-2.9,6.9h-30.9
c-2.5,0-4.4-0.9-5.5-2.6l-29.3-43.8c-0.2-0.6-0.9-1.7-2.1-3.1c-1.3-1.4-1.9-2.2-1.9-2.4l-24-34.7l-2.9-3.1l-0.5,1
c0,58.9-0.7,88.3-2.1,88.3h-37.6l-1.4-0.7c0-12.2,0.2-33.2,0.5-63c0.3-29.8,0.5-51.8,0.5-66.3c0-10.9-0.2-19.2-0.5-24.8l1-6.9
l4.8-1l30,1c0.8,0,1.7,0.8,2.9,2.4c1.1,1.6,1.8,2.9,2.1,3.8l22.4,32.4c0.3,0.6,0.9,1.5,1.8,2.7c0.9,1.2,1.5,2.1,1.8,2.7l30.7,42.1
c0.5,1.1,1.1,1.7,1.9,1.7c0.5,0,0.7-0.2,0.7-0.7v-85.9l1.4-1.2c0.2-0.2,3.6-0.2,10.2-0.2c18.7,0,28.1,0.3,28.1,1l1,1.4V426.3z
M1513.3,448.5c-2.4-2.4-5.4-4.8-9.2-7.3c-3.7-2.5-8-5.1-12.9-7.9c-4.8-2.8-7.8-4.6-8.9-5.4c-20.5-13-30.7-28.7-30.7-47.1
c0-15.4,5.4-26.9,16.2-34.5c10.8-7.6,24.8-11.4,41.9-11.4c9.4,0,18.8,1.9,28.3,5.7c1.7,0.6,4.6,1.9,8.4,3.8c3.9,1.9,5.8,3.3,5.8,4
l-0.2,1.2c0,0.2-0.2,0.4-0.5,0.8c-0.3,0.4-0.5,0.8-0.5,1.1c-4.9,10.2-9.7,18.4-14.3,24.8c-0.2,0.5-0.6,0.7-1.2,0.7
c-0.6,0-4.3-1.3-10.9-3.9c-6.7-2.6-11.8-3.9-15.5-3.9c-3.5,0-6.7,1-9.8,2.9c-3,1.9-4.5,4.5-4.5,7.9c0,4.3,2.4,8.3,7.1,12.1
c3.2,2.4,9.5,6.6,19,12.6c2.1,1.4,4.8,3.3,8.1,5.6c3.3,2.3,5.9,4.1,7.9,5.5c1.9,1.3,4,3,6.4,5c2.4,2,4.4,3.9,5.9,5.8
c3.8,4.9,6.4,9.4,7.7,13.3c1.3,4,2,8.8,2,14.5c0,14.9-5.6,27.2-16.9,36.8c-11.3,9.6-24.7,14.4-40.2,14.4c-17,0-32.9-4.1-47.8-12.4
c-6.7-4.1-10-6.6-10-7.4v-1c7-12.7,12.8-22.5,17.4-29.5c2.2,0,4.8,1.1,7.7,3.3c2.9,2.2,4.6,3.4,5.1,3.6c8.4,4.3,18,6.4,28.8,6.4
c9.4,0,14-3.5,14-10.5C1517.4,455.2,1516,452,1513.3,448.5z M1671.1,335h28.3c0.8,0,3.1,4.4,6.8,13.1c3.7,8.7,5.6,13.6,5.6,14.5
c3.3,8.7,21.3,53.1,54,133c0,2.2-1,3.6-2.9,4.2c-1.9,0.6-5.4,0.8-10.5,0.8l-12.1-0.2c-8.1-0.2-14.2-0.2-18.3-0.2
c-0.3-1-1-2.6-1.9-5c-1-2.4-1.7-4.2-2.3-5.5c-0.6-1.3-1.4-2.9-2.5-4.8c-1.1-1.9-2.3-3.6-3.6-5.2c-1.1,0-3.3-0.2-6.7-0.6
c-3.3-0.4-6.3-0.6-9-0.6l-29.8,0.5c-0.5,0-1.9-0.1-4.3-0.2c-2.4-0.2-3.7-0.2-3.8-0.2c-2.9,0-5.3,3.7-7.4,11.2
c-2.1,7.5-3.8,11.7-5.2,12.6c-4.3,0.2-9.6,0-15.8-0.4c-6.3-0.4-11.5-0.6-15.8-0.6c-6.7,0-10-1-10-2.9c0-0.3,0-0.7,0.1-1.2
c0.1-0.5,0.1-0.8,0.1-1l13.8-40c2.1-4.1,4.6-10.3,7.5-18.6c2.9-8.2,4.8-13.3,5.6-15.2c3.2-8.2,8.6-22,16.3-41.2
c7.7-19.2,13.5-34,17.5-44.5C1665.1,335.6,1667.2,335,1671.1,335z M1673.5,446.1h6.2c11.4-0.3,17.1-1,17.1-1.9
c0-2.9-2.5-10.1-7.4-21.7c-1.9-4.6-3-7.1-3.3-7.6l-3.1-7.4c-0.8,0-3.5,5.5-8,16.4s-6.8,17.4-6.8,19.3
C1668.3,445.1,1670,446.1,1673.5,446.1z M1851.1,334c4.9,0,7.4,0.9,7.4,2.6v48.6c0,1.6,0.2,2.4,0.5,2.4c1.1,0,2.5-1,4.2-3.1
c1.7-2.1,3.4-4.4,5.1-7c1.7-2.6,3.1-4.3,4-5.1l26.7-36.2c0.8-1,1.9-1.4,3.3-1.4h35.2l5.7,1c0.3,0,0.5,0.3,0.5,1
c0,1.1-0.2,1.8-0.5,2.1l-10.2,16.2l-1.4,1l-30.5,40.7c-0.5,0.5-0.7,1.1-0.7,1.9c0,1.1,0.2,2.1,0.7,2.9l46.2,95.4c0,0.2,0,0.4,0,0.7
l0.2,0.7c0,2.7-4.8,4-14.3,4c-5.6,0-12-0.2-19.4-0.7c-7.4-0.5-11.5-0.7-12.3-0.7l-1.7-1.2c-1-1-9.8-19.3-26.7-55
c-0.2-1.3-1.3-3.8-3.3-7.6c-0.5,0-2.3,2.2-5.6,6.7c-3.3,4.4-5,7.1-5.4,7.9c0,2.9,0,7.5,0.1,13.8c0.1,6.3,0.1,12.3,0.1,17.8
c0,11.3-1.3,17.1-3.8,17.6c-3.2,0-7.1,0.1-11.9,0.4c-4.8,0.2-8,0.4-9.8,0.4c-2.7,0-6.3-0.3-10.9-1c-4.6-0.6-6.9-1.2-6.9-1.7v-81.9
c1-7.6,1.4-12,1.4-13.1c0-7.1-0.3-17.8-1-32.1c-0.6-14.3-1-24.9-1-31.9c0-2.9,0.6-4.6,1.9-5.2C1825.4,334.2,1836.6,334,1851.1,334z
"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.4 KiB

@ -0,0 +1 @@
export const TRANSAK_API_KEY = '25ac1309-a49b-4411-b20e-5e56c61a5b1c'; // It's a public key, which will be included in a URL for Transak.

@ -11,7 +11,7 @@ import PreferencesController from './preferences';
describe('DetectTokensController', function () {
const sandbox = sinon.createSandbox();
let keyringMemStore, network, preferences;
let keyringMemStore, network, preferences, provider;
const noop = () => undefined;
@ -23,12 +23,19 @@ describe('DetectTokensController', function () {
keyringMemStore = new ObservableStore({ isUnlocked: false });
network = new NetworkController();
network.setInfuraProjectId('foo');
preferences = new PreferencesController({ network });
network.initializeProvider(networkControllerProviderConfig);
provider = network.getProviderAndBlockTracker().provider;
preferences = new PreferencesController({ network, provider });
preferences.setAddresses([
'0x7e57e2',
'0xbc86727e770de68b1060c91f6bb6945c73e10388',
]);
network.initializeProvider(networkControllerProviderConfig);
sandbox
.stub(network, 'getLatestBlock')
.callsFake(() => Promise.resolve({}));
sandbox
.stub(preferences, '_detectIsERC721')
.returns(Promise.resolve(false));
});
after(function () {
@ -125,6 +132,7 @@ describe('DetectTokensController', function () {
address: existingTokenAddress.toLowerCase(),
decimals: existingToken.decimals,
symbol: existingToken.symbol,
isERC721: false,
},
]);
});
@ -177,11 +185,13 @@ describe('DetectTokensController', function () {
address: existingTokenAddress.toLowerCase(),
decimals: existingToken.decimals,
symbol: existingToken.symbol,
isERC721: false,
},
{
address: tokenAddressToAdd.toLowerCase(),
decimals: tokenToAdd.decimals,
symbol: tokenToAdd.symbol,
isERC721: false,
},
]);
});
@ -234,11 +244,13 @@ describe('DetectTokensController', function () {
address: existingTokenAddress.toLowerCase(),
decimals: existingToken.decimals,
symbol: existingToken.symbol,
isERC721: false,
},
{
address: tokenAddressToAdd.toLowerCase(),
decimals: tokenToAdd.decimals,
symbol: tokenToAdd.symbol,
isERC721: false,
},
]);
});

@ -34,8 +34,10 @@ const fetchWithTimeout = getFetchWithTimeout(SECOND * 30);
* @typedef {Object} EtherscanTransaction
* @property {string} blockNumber - The number of the block this transaction was found in, in decimal
* @property {string} from - The hex-prefixed address of the sender
* @property {string} gas - The gas limit, in decimal WEI
* @property {string} gasPrice - The gas price, in decimal WEI
* @property {string} gas - The gas limit, in decimal GWEI
* @property {string} [gasPrice] - The gas price, in decimal WEI
* @property {string} [maxFeePerGas] - The maximum fee per gas, inclusive of tip, in decimal WEI
* @property {string} [maxPriorityFeePerGas] - The maximum tip per gas in decimal WEI
* @property {string} hash - The hex-prefixed transaction hash
* @property {string} isError - Whether the transaction was confirmed or failed (0 for confirmed, 1 for failed)
* @property {string} nonce - The transaction nonce, in decimal
@ -267,6 +269,25 @@ export default class IncomingTransactionsController {
etherscanTransaction.isError === '0'
? TRANSACTION_STATUSES.CONFIRMED
: TRANSACTION_STATUSES.FAILED;
const txParams = {
from: etherscanTransaction.from,
gas: bnToHex(new BN(etherscanTransaction.gas)),
nonce: bnToHex(new BN(etherscanTransaction.nonce)),
to: etherscanTransaction.to,
value: bnToHex(new BN(etherscanTransaction.value)),
};
if (etherscanTransaction.gasPrice) {
txParams.gasPrice = bnToHex(new BN(etherscanTransaction.gasPrice));
} else if (etherscanTransaction.maxFeePerGas) {
txParams.maxFeePerGas = bnToHex(
new BN(etherscanTransaction.maxFeePerGas),
);
txParams.maxPriorityFeePerGas = bnToHex(
new BN(etherscanTransaction.maxPriorityFeePerGas),
);
}
return {
blockNumber: etherscanTransaction.blockNumber,
id: createId(),
@ -274,14 +295,7 @@ export default class IncomingTransactionsController {
metamaskNetworkId: CHAIN_ID_TO_NETWORK_ID_MAP[chainId],
status,
time,
txParams: {
from: etherscanTransaction.from,
gas: bnToHex(new BN(etherscanTransaction.gas)),
gasPrice: bnToHex(new BN(etherscanTransaction.gasPrice)),
nonce: bnToHex(new BN(etherscanTransaction.nonce)),
to: etherscanTransaction.to,
value: bnToHex(new BN(etherscanTransaction.value)),
},
txParams,
hash: etherscanTransaction.hash,
type: TRANSACTION_TYPES.INCOMING,
};

@ -103,15 +103,34 @@ function getMockBlockTracker() {
/**
* Returns a transaction object matching the expected format returned
* by the Etherscan API
*
* @param {string} [toAddress] - The hex-prefixed address of the recipient
* @param {number} [blockNumber] - The block number for the transaction
* @param {Object} [params] - options bag
* @param {string} [params.toAddress] - The hex-prefixed address of the recipient
* @param {number} [params.blockNumber] - The block number for the transaction
* @param {boolean} [params.useEIP1559] - Use EIP-1559 gas fields
* @param
* @returns {EtherscanTransaction}
*/
const getFakeEtherscanTransaction = (
const getFakeEtherscanTransaction = ({
toAddress = MOCK_SELECTED_ADDRESS,
blockNumber = 10,
) => {
useEIP1559 = false,
hash = '0xfake',
} = {}) => {
if (useEIP1559) {
return {
blockNumber: blockNumber.toString(),
from: '0xfake',
gas: '0',
maxFeePerGas: '10',
maxPriorityFeePerGas: '1',
hash,
isError: '0',
nonce: '100',
timeStamp: '16000000000000',
to: toAddress,
value: '0',
};
}
return {
blockNumber: blockNumber.toString(),
from: '0xfake',
@ -243,7 +262,13 @@ describe('IncomingTransactionsController', function () {
200,
JSON.stringify({
status: '1',
result: [getFakeEtherscanTransaction()],
result: [
getFakeEtherscanTransaction(),
getFakeEtherscanTransaction({
hash: '0xfakeeip1559',
useEIP1559: true,
}),
],
}),
);
const updateStateStub = sinon.stub(
@ -263,6 +288,9 @@ describe('IncomingTransactionsController', function () {
const actualStateWithoutGenerated = cloneDeep(actualState);
delete actualStateWithoutGenerated?.incomingTransactions?.['0xfake']?.id;
delete actualStateWithoutGenerated?.incomingTransactions?.[
'0xfakeeip1559'
]?.id;
assert.ok(
typeof generatedTxId === 'number' && generatedTxId > 0,
@ -290,6 +318,24 @@ describe('IncomingTransactionsController', function () {
value: '0x0',
},
},
'0xfakeeip1559': {
blockNumber: '10',
hash: '0xfakeeip1559',
metamaskNetworkId: ROPSTEN_NETWORK_ID,
chainId: ROPSTEN_CHAIN_ID,
status: TRANSACTION_STATUSES.CONFIRMED,
time: 16000000000000000,
type: TRANSACTION_TYPES.INCOMING,
txParams: {
from: '0xfake',
gas: '0x0',
maxFeePerGas: '0xa',
maxPriorityFeePerGas: '0x1',
nonce: '0x64',
to: '0x0101',
value: '0x0',
},
},
},
incomingTxLastFetchedBlockByChainId: {
...getNonEmptyInitState().incomingTxLastFetchedBlockByChainId,
@ -509,7 +555,11 @@ describe('IncomingTransactionsController', function () {
200,
JSON.stringify({
status: '1',
result: [getFakeEtherscanTransaction(NEW_MOCK_SELECTED_ADDRESS)],
result: [
getFakeEtherscanTransaction({
toAddress: NEW_MOCK_SELECTED_ADDRESS,
}),
],
}),
);
const updateStateStub = sinon.stub(
@ -586,7 +636,9 @@ describe('IncomingTransactionsController', function () {
// reply with a valid request for any supported network, so that this test has every opportunity to fail
nockEtherscanApiForAllChains({
status: '1',
result: [getFakeEtherscanTransaction(NEW_MOCK_SELECTED_ADDRESS)],
result: [
getFakeEtherscanTransaction({ toAddress: NEW_MOCK_SELECTED_ADDRESS }),
],
});
const updateStateStub = sinon.stub(
incomingTransactionsController.store,
@ -954,7 +1006,9 @@ describe('IncomingTransactionsController', function () {
describe('_getNewIncomingTransactions', function () {
const ADDRESS_TO_FETCH_FOR = '0xfakeaddress';
const FETCHED_TX = getFakeEtherscanTransaction(ADDRESS_TO_FETCH_FOR);
const FETCHED_TX = getFakeEtherscanTransaction({
toAddress: ADDRESS_TO_FETCH_FOR,
});
const mockFetch = sinon.stub().returns(
Promise.resolve({
json: () => Promise.resolve({ status: '1', result: [FETCHED_TX] }),
@ -1212,5 +1266,53 @@ describe('IncomingTransactionsController', function () {
type: TRANSACTION_TYPES.INCOMING,
});
});
it('should return the expected data when the tx uses EIP-1559 fields', function () {
const incomingTransactionsController = new IncomingTransactionsController(
{
blockTracker: getMockBlockTracker(),
...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(),
},
);
const result = incomingTransactionsController._normalizeTxFromEtherscan(
{
timeStamp: '4444',
isError: '0',
blockNumber: 333,
from: '0xa',
gas: '11',
maxFeePerGas: '12',
maxPriorityFeePerGas: '1',
nonce: '13',
to: '0xe',
value: '15',
hash: '0xg',
},
ROPSTEN_CHAIN_ID,
);
assert.deepStrictEqual(result, {
blockNumber: 333,
id: 54321,
metamaskNetworkId: ROPSTEN_NETWORK_ID,
chainId: ROPSTEN_CHAIN_ID,
status: TRANSACTION_STATUSES.CONFIRMED,
time: 4444000,
txParams: {
from: '0xa',
gas: '0xb',
maxFeePerGas: '0xc',
maxPriorityFeePerGas: '0x1',
nonce: '0xd',
to: '0xe',
value: '0xf',
},
hash: '0xg',
type: TRANSACTION_TYPES.INCOMING,
});
});
});
});

@ -1,11 +1,13 @@
import { strict as assert } from 'assert';
import sinon from 'sinon';
import { getNetworkDisplayName } from './util';
import NetworkController from './network';
import NetworkController, { NETWORK_EVENTS } from './network';
describe('NetworkController', function () {
describe('controller', function () {
let networkController;
let getLatestBlockStub;
let setProviderTypeAndWait;
const noop = () => undefined;
const networkControllerProviderConfig = {
getAccounts: noop,
@ -13,7 +15,21 @@ describe('NetworkController', function () {
beforeEach(function () {
networkController = new NetworkController();
getLatestBlockStub = sinon
.stub(networkController, 'getLatestBlock')
.callsFake(() => Promise.resolve({}));
networkController.setInfuraProjectId('foo');
setProviderTypeAndWait = () =>
new Promise((resolve) => {
networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => {
resolve();
});
networkController.setProviderType('mainnet');
});
});
afterEach(function () {
getLatestBlockStub.reset();
});
describe('#provider', function () {
@ -67,6 +83,59 @@ describe('NetworkController', function () {
);
});
});
describe('#getEIP1559Compatibility', function () {
it('should return false when baseFeePerGas is not in the block header', async function () {
networkController.initializeProvider(networkControllerProviderConfig);
const supportsEIP1559 = await networkController.getEIP1559Compatibility();
assert.equal(supportsEIP1559, false);
});
it('should return true when baseFeePerGas is in block header', async function () {
networkController.initializeProvider(networkControllerProviderConfig);
getLatestBlockStub.callsFake(() =>
Promise.resolve({ baseFeePerGas: '0xa ' }),
);
const supportsEIP1559 = await networkController.getEIP1559Compatibility();
assert.equal(supportsEIP1559, true);
});
it('should store EIP1559 support in state to reduce calls to getLatestBlock', async function () {
networkController.initializeProvider(networkControllerProviderConfig);
getLatestBlockStub.callsFake(() =>
Promise.resolve({ baseFeePerGas: '0xa ' }),
);
await networkController.getEIP1559Compatibility();
const supportsEIP1559 = await networkController.getEIP1559Compatibility();
assert.equal(getLatestBlockStub.calledOnce, true);
assert.equal(supportsEIP1559, true);
});
it('should clear stored EIP1559 support when changing networks', async function () {
networkController.initializeProvider(networkControllerProviderConfig);
networkController.consoleThis = true;
getLatestBlockStub.callsFake(() =>
Promise.resolve({ baseFeePerGas: '0xa ' }),
);
await networkController.getEIP1559Compatibility();
assert.equal(
networkController.networkDetails.getState().EIPS[1559],
true,
);
getLatestBlockStub.callsFake(() => Promise.resolve({}));
await setProviderTypeAndWait('mainnet');
assert.equal(
networkController.networkDetails.getState().EIPS[1559],
undefined,
);
await networkController.getEIP1559Compatibility();
assert.equal(
networkController.networkDetails.getState().EIPS[1559],
false,
);
assert.equal(getLatestBlockStub.calledTwice, true);
});
});
});
describe('utils', function () {

@ -51,6 +51,10 @@ const defaultProviderConfig = {
...defaultProviderConfigOpts,
};
const defaultNetworkDetailsState = {
EIPS: { 1559: undefined },
};
export const NETWORK_EVENTS = {
// Fired after the actively selected network is changed
NETWORK_DID_CHANGE: 'networkDidChange',
@ -74,10 +78,21 @@ export default class NetworkController extends EventEmitter {
this.providerStore.getState(),
);
this.networkStore = new ObservableStore('loading');
// We need to keep track of a few details about the current network
// Ideally we'd merge this.networkStore with this new store, but doing so
// will require a decent sized refactor of how we're accessing network
// state. Currently this is only used for detecting EIP 1559 support but
// can be extended to track other network details.
this.networkDetails = new ObservableStore(
opts.networkDetails || {
...defaultNetworkDetailsState,
},
);
this.store = new ComposedStore({
provider: this.providerStore,
previousProviderStore: this.previousProviderStore,
network: this.networkStore,
networkDetails: this.networkDetails,
});
// provider and block tracker
@ -120,6 +135,42 @@ export default class NetworkController extends EventEmitter {
return { provider, blockTracker };
}
/**
* Method to return the latest block for the current network
* @returns {Object} Block header
*/
getLatestBlock() {
return new Promise((resolve, reject) => {
const { provider } = this.getProviderAndBlockTracker();
const ethQuery = new EthQuery(provider);
ethQuery.sendAsync(
{ method: 'eth_getBlockByNumber', params: ['latest', false] },
(err, block) => {
if (err) {
return reject(err);
}
return resolve(block);
},
);
});
}
/**
* Method to check if the block header contains fields that indicate EIP 1559
* support (baseFeePerGas).
* @returns {Promise<boolean>} true if current network supports EIP 1559
*/
async getEIP1559Compatibility() {
const { EIPS } = this.networkDetails.getState();
if (EIPS[1559] !== undefined) {
return EIPS[1559];
}
const latestBlock = await this.getLatestBlock();
const supportsEIP1559 = latestBlock.baseFeePerGas !== undefined;
this.setNetworkEIPSupport(1559, supportsEIP1559);
return supportsEIP1559;
}
verifyNetwork() {
// Check network when restoring connectivity:
if (this.isNetworkLoading()) {
@ -135,6 +186,26 @@ export default class NetworkController extends EventEmitter {
this.networkStore.putState(network);
}
/**
* Set EIP support indication in the networkDetails store
* @param {number} EIPNumber - The number of the EIP to mark support for
* @param {boolean} isSupported - True if the EIP is supported
*/
setNetworkEIPSupport(EIPNumber, isSupported) {
this.networkDetails.updateState({
EIPS: {
[EIPNumber]: isSupported,
},
});
}
/**
* Reset EIP support to default (no support)
*/
clearNetworkDetails() {
this.networkDetails.putState({ ...defaultNetworkDetailsState });
}
isNetworkLoading() {
return this.getNetworkState() === 'loading';
}
@ -154,6 +225,8 @@ export default class NetworkController extends EventEmitter {
'NetworkController - lookupNetwork aborted due to missing chainId',
);
this.setNetworkState('loading');
// keep network details in sync with network state
this.clearNetworkDetails();
return;
}
@ -174,10 +247,14 @@ export default class NetworkController extends EventEmitter {
if (initialNetwork === currentNetwork) {
if (err) {
this.setNetworkState('loading');
// keep network details in sync with network state
this.clearNetworkDetails();
return;
}
this.setNetworkState(networkVersion);
// look up EIP-1559 support
this.getEIP1559Compatibility();
}
});
}
@ -298,9 +375,15 @@ export default class NetworkController extends EventEmitter {
}
_switchNetwork(opts) {
// Indicate to subscribers that network is about to change
this.emit(NETWORK_EVENTS.NETWORK_WILL_CHANGE);
// Set loading state
this.setNetworkState('loading');
// Reset network details
this.clearNetworkDetails();
// Configure the provider appropriately
this._configureProvider(opts);
// Notify subscribers that network has changed
this.emit(NETWORK_EVENTS.NETWORK_DID_CHANGE, opts.type);
}

@ -2,14 +2,21 @@ import { strict as assert } from 'assert';
import { ObservableStore } from '@metamask/obs-store';
import { ethErrors } from 'eth-rpc-errors';
import { normalize as normalizeAddress } from 'eth-sig-util';
import ethers from 'ethers';
import { ethers } from 'ethers';
import log from 'loglevel';
import abiERC721 from 'human-standard-collectible-abi';
import contractsMap from '@metamask/contract-metadata';
import { LISTED_CONTRACT_ADDRESSES } from '../../../shared/constants/tokens';
import { NETWORK_TYPE_TO_ID_MAP } from '../../../shared/constants/network';
import { isPrefixedFormattedHexString } from '../../../shared/modules/network.utils';
import { isValidHexAddress } from '../../../shared/modules/hexstring-utils';
import {
isValidHexAddress,
toChecksumHexAddress,
} from '../../../shared/modules/hexstring-utils';
import { NETWORK_EVENTS } from './network';
const ERC721METADATA_INTERFACE_ID = '0x5b5e139f';
export default class PreferencesController {
/**
*
@ -73,11 +80,18 @@ export default class PreferencesController {
};
this.network = opts.network;
this.ethersProvider = new ethers.providers.Web3Provider(opts.provider);
this.store = new ObservableStore(initState);
this.store.setMaxListeners(12);
this.openPopup = opts.openPopup;
this.migrateAddressBookState = opts.migrateAddressBookState;
this._subscribeToNetworkDidChange();
this.network.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => {
const { tokens, hiddenTokens } = this._getTokenRelatedStates();
this.ethersProvider = new ethers.providers.Web3Provider(opts.provider);
this._updateAccountTokens(tokens, this.getAssetImages(), hiddenTokens);
});
this._subscribeToInfuraAvailability();
global.setPreference = (key, value) => {
@ -393,6 +407,8 @@ export default class PreferencesController {
});
const previousIndex = tokens.indexOf(previousEntry);
newEntry.isERC721 = await this._detectIsERC721(newEntry.address);
if (previousEntry) {
tokens[previousIndex] = newEntry;
} else {
@ -403,6 +419,24 @@ export default class PreferencesController {
return Promise.resolve(tokens);
}
/**
* Adds isERC721 field to token object
* (Called when a user attempts to add tokens that were previously added which do not yet had isERC721 field)
*
* @param {string} tokenAddress - The contract address of the token requiring the isERC721 field added.
* @returns {Promise<object>} The new token object with the added isERC721 field.
*
*/
async updateTokenType(tokenAddress) {
const { tokens } = this.store.getState();
const tokenIndex = tokens.findIndex((token) => {
return token.address === tokenAddress;
});
tokens[tokenIndex].isERC721 = await this._detectIsERC721(tokenAddress);
this.store.updateState({ tokens });
return Promise.resolve(tokens[tokenIndex]);
}
/**
* Removes a specified token from the tokens array and adds it to hiddenTokens array
*
@ -480,11 +514,8 @@ export default class PreferencesController {
let addressBookKey = rpcDetail.chainId;
if (!addressBookKey) {
// We need to find the networkId to determine what these addresses were keyed by
const provider = new ethers.providers.JsonRpcProvider(
rpcDetail.rpcUrl,
);
try {
addressBookKey = await provider.send('net_version');
addressBookKey = await this.ethersProvider.send('net_version');
assert(typeof addressBookKey === 'string');
} catch (error) {
log.debug(error);
@ -701,17 +732,6 @@ export default class PreferencesController {
// PRIVATE METHODS
//
/**
* Handle updating token list to reflect current network by listening for the
* NETWORK_DID_CHANGE event.
*/
_subscribeToNetworkDidChange() {
this.network.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => {
const { tokens, hiddenTokens } = this._getTokenRelatedStates();
this._updateAccountTokens(tokens, this.getAssetImages(), hiddenTokens);
});
}
_subscribeToInfuraAvailability() {
this.network.on(NETWORK_EVENTS.INFURA_IS_BLOCKED, () => {
this._setInfuraBlocked(true);
@ -763,6 +783,43 @@ export default class PreferencesController {
});
}
/**
* Detects whether or not a token is ERC-721 compatible.
*
* @param {string} tokensAddress - the token contract address.
*
*/
async _detectIsERC721(tokenAddress) {
const checksumAddress = toChecksumHexAddress(tokenAddress);
// if this token is already in our contract metadata map we don't need
// to check against the contract
if (contractsMap[checksumAddress]?.erc721 === true) {
return Promise.resolve(true);
}
const tokenContract = await this._createEthersContract(
tokenAddress,
abiERC721,
this.ethersProvider,
);
return await tokenContract
.supportsInterface(ERC721METADATA_INTERFACE_ID)
.catch((error) => {
console.log('error', error);
log.debug(error);
return false;
});
}
async _createEthersContract(tokenAddress, abi, ethersProvider) {
const tokenContract = await new ethers.Contract(
tokenAddress,
abi,
ethersProvider,
);
return tokenContract;
}
/**
* Updates `tokens` and `hiddenTokens` of current account and network.
*

@ -1,10 +1,13 @@
import { strict as assert } from 'assert';
import sinon from 'sinon';
import contractMaps from '@metamask/contract-metadata';
import abiERC721 from 'human-standard-collectible-abi';
import {
MAINNET_CHAIN_ID,
RINKEBY_CHAIN_ID,
} from '../../../shared/constants/network';
import PreferencesController from './preferences';
import NetworkController from './network';
describe('preferences controller', function () {
let preferencesController;
@ -13,19 +16,35 @@ describe('preferences controller', function () {
let triggerNetworkChange;
let switchToMainnet;
let switchToRinkeby;
let provider;
const migrateAddressBookState = sinon.stub();
beforeEach(function () {
const sandbox = sinon.createSandbox();
currentChainId = MAINNET_CHAIN_ID;
network = {
getCurrentChainId: () => currentChainId,
on: sinon.spy(),
const networkControllerProviderConfig = {
getAccounts: () => undefined,
};
network = new NetworkController();
network.setInfuraProjectId('foo');
network.initializeProvider(networkControllerProviderConfig);
provider = network.getProviderAndBlockTracker().provider;
sandbox
.stub(network, 'getLatestBlock')
.callsFake(() => Promise.resolve({}));
sandbox.stub(network, 'getCurrentChainId').callsFake(() => currentChainId);
sandbox
.stub(network, 'getProviderConfig')
.callsFake(() => ({ type: 'mainnet' }));
const spy = sandbox.spy(network, 'on');
preferencesController = new PreferencesController({
migrateAddressBookState,
network,
provider,
});
triggerNetworkChange = network.on.firstCall.args[1];
triggerNetworkChange = spy.firstCall.args[1];
switchToMainnet = () => {
currentChainId = MAINNET_CHAIN_ID;
triggerNetworkChange();
@ -86,6 +105,104 @@ describe('preferences controller', function () {
});
});
describe('updateTokenType', function () {
it('should add isERC721 = true to token object in state when token is collectible and in our contract-metadata repo', async function () {
const contractAddresses = Object.keys(contractMaps);
const erc721ContractAddresses = contractAddresses.filter(
(contractAddress) => contractMaps[contractAddress].erc721 === true,
);
const address = erc721ContractAddresses[0];
const { symbol, decimals } = contractMaps[address];
preferencesController.store.updateState({
tokens: [{ address, symbol, decimals }],
});
const result = await preferencesController.updateTokenType(address);
assert.equal(result.isERC721, true);
});
it('should add isERC721 = true to token object in state when token is collectible and not in our contract-metadata repo', async function () {
const tokenAddress = '0xda5584cc586d07c7141aa427224a4bd58e64af7d';
preferencesController.store.updateState({
tokens: [
{
address: tokenAddress,
symbol: 'TESTNFT',
decimals: '0',
},
],
});
sinon
.stub(preferencesController, '_detectIsERC721')
.callsFake(() => true);
const result = await preferencesController.updateTokenType(tokenAddress);
assert.equal(
preferencesController._detectIsERC721.getCall(0).args[0],
tokenAddress,
);
assert.equal(result.isERC721, true);
});
});
describe('_detectIsERC721', function () {
it('should return true when token is in our contract-metadata repo', async function () {
const tokenAddress = '0x06012c8cf97BEaD5deAe237070F9587f8E7A266d';
const result = await preferencesController._detectIsERC721(tokenAddress);
assert.equal(result, true);
});
it('should return true when the token is not in our contract-metadata repo but tokenContract.supportsInterface returns true', async function () {
const tokenAddress = '0xda5584cc586d07c7141aa427224a4bd58e64af7d';
const supportsInterfaceStub = sinon.stub().returns(Promise.resolve(true));
sinon
.stub(preferencesController, '_createEthersContract')
.callsFake(() => ({ supportsInterface: supportsInterfaceStub }));
const result = await preferencesController._detectIsERC721(tokenAddress);
assert.equal(
preferencesController._createEthersContract.getCall(0).args[0],
tokenAddress,
);
assert.deepEqual(
preferencesController._createEthersContract.getCall(0).args[1],
abiERC721,
);
assert.equal(
preferencesController._createEthersContract.getCall(0).args[2],
preferencesController.ethersProvider,
);
assert.equal(result, true);
});
it('should return false when the token is not in our contract-metadata repo and tokenContract.supportsInterface returns false', async function () {
const tokenAddress = '0xda5584cc586d07c7141aa427224a4bd58e64af7d';
const supportsInterfaceStub = sinon
.stub()
.returns(Promise.resolve(false));
sinon
.stub(preferencesController, '_createEthersContract')
.callsFake(() => ({ supportsInterface: supportsInterfaceStub }));
const result = await preferencesController._detectIsERC721(tokenAddress);
assert.equal(
preferencesController._createEthersContract.getCall(0).args[0],
tokenAddress,
);
assert.deepEqual(
preferencesController._createEthersContract.getCall(0).args[1],
abiERC721,
);
assert.equal(
preferencesController._createEthersContract.getCall(0).args[2],
preferencesController.ethersProvider,
);
assert.equal(result, false);
});
});
describe('removeAddress', function () {
it('should remove an address from state', function () {
preferencesController.setAddresses(['0xda22le', '0x7e57e2']);
@ -291,7 +408,12 @@ describe('preferences controller', function () {
assert.equal(tokens.length, 1, 'one token removed');
const [token1] = tokens;
assert.deepEqual(token1, { address: '0xb', symbol: 'B', decimals: 5 });
assert.deepEqual(token1, {
address: '0xb',
symbol: 'B',
decimals: 5,
isERC721: false,
});
});
it('should remove a token from its state on corresponding address', async function () {
@ -310,7 +432,12 @@ describe('preferences controller', function () {
assert.equal(tokensFirst.length, 1, 'one token removed in account');
const [token1] = tokensFirst;
assert.deepEqual(token1, { address: '0xb', symbol: 'B', decimals: 5 });
assert.deepEqual(token1, {
address: '0xb',
symbol: 'B',
decimals: 5,
isERC721: false,
});
await preferencesController.setSelectedAddress('0x7e57e3');
const tokensSecond = preferencesController.getTokens();
@ -335,7 +462,12 @@ describe('preferences controller', function () {
assert.equal(tokensFirst.length, 1, 'one token removed in network');
const [token1] = tokensFirst;
assert.deepEqual(token1, { address: '0xb', symbol: 'B', decimals: 5 });
assert.deepEqual(token1, {
address: '0xb',
symbol: 'B',
decimals: 5,
isERC721: false,
});
switchToRinkeby();
const tokensSecond = preferencesController.getTokens();

@ -1,10 +1,11 @@
import EventEmitter from 'safe-event-emitter';
import { ObservableStore } from '@metamask/obs-store';
import { bufferToHex, keccak, toBuffer } from 'ethereumjs-util';
import Transaction from 'ethereumjs-tx';
import EthQuery from 'ethjs-query';
import { ethErrors } from 'eth-rpc-errors';
import abi from 'human-standard-token-abi';
import Common from '@ethereumjs/common';
import { TransactionFactory } from '@ethereumjs/tx';
import { ethers } from 'ethers';
import NonceTracker from 'nonce-tracker';
import log from 'loglevel';
@ -24,6 +25,11 @@ import {
} from '../../../../shared/constants/transaction';
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
import { GAS_LIMITS } from '../../../../shared/constants/gas';
import {
HARDFORKS,
MAINNET,
NETWORK_TYPE_RPC,
} from '../../../../shared/constants/network';
import { isEIP1559Transaction } from '../../../../shared/modules/transaction.utils';
import TransactionStateManager from './tx-state-manager';
import TxGasUtil from './tx-gas-utils';
@ -62,7 +68,7 @@ export const TRANSACTION_EVENTS = {
@param {Object} opts.networkStore - an observable store for network number
@param {Object} opts.blockTracker - An instance of eth-blocktracker
@param {Object} opts.provider - A network provider.
@param {Function} opts.signTransaction - function the signs an ethereumjs-tx
@param {Function} opts.signTransaction - function the signs an @ethereumjs/tx
@param {Object} opts.getPermittedAccounts - get accounts that an origin has permissions for
@param {Function} opts.signTransaction - ethTx signer that returns a rawTx
@param {number} [opts.txHistoryLimit] - number *optional* for limiting how many transactions are in state
@ -74,6 +80,8 @@ export default class TransactionController extends EventEmitter {
super();
this.networkStore = opts.networkStore || new ObservableStore({});
this._getCurrentChainId = opts.getCurrentChainId;
this.getProviderConfig = opts.getProviderConfig;
this.getEIP1559Compatibility = opts.getEIP1559Compatibility;
this.preferencesStore = opts.preferencesStore || new ObservableStore({});
this.provider = opts.provider;
this.getPermittedAccounts = opts.getPermittedAccounts;
@ -155,6 +163,58 @@ export default class TransactionController extends EventEmitter {
return integerChainId;
}
/**
* @ethereumjs/tx uses @ethereumjs/common as a configuration tool for
* specifying which chain, network, hardfork and EIPs to support for
* a transaction. By referencing this configuration, and analyzing the fields
* specified in txParams, @ethereumjs/tx is able to determine which EIP-2718
* transaction type to use.
* @returns {Common} common configuration object
*/
async getCommonConfiguration() {
const { type, nickname: name } = this.getProviderConfig();
const supportsEIP1559 = await this.getEIP1559Compatibility();
// This logic below will have to be updated each time a hardfork happens
// that carries with it a new Transaction type. It is inconsequential for
// hardforks that do not include new types.
const hardfork = supportsEIP1559 ? HARDFORKS.LONDON : HARDFORKS.BERLIN;
// type will be one of our default network names or 'rpc'. the default
// network names are sufficient configuration, simply pass the name as the
// chain argument in the constructor.
if (type !== NETWORK_TYPE_RPC) {
return new Common({
chain: type,
hardfork,
});
}
// For 'rpc' we need to use the same basic configuration as mainnet,
// since we only support EVM compatible chains, and then override the
// name, chainId and networkId properties. This is done using the
// `forCustomChain` static method on the Common class.
const chainId = parseInt(this._getCurrentChainId(), 16);
const networkId = this.networkStore.getState();
const customChainParams = {
name,
chainId,
// It is improbable for a transaction to be signed while the network
// is loading for two reasons.
// 1. Pending, unconfirmed transactions are wiped on network change
// 2. The UI is unusable (loading indicator) when network is loading.
// setting the networkId to 0 is for type safety and to explicity lead
// the transaction to failing if a user is able to get to this branch
// on a custom network that requires valid network id. I have not ran
// into this limitation on any network I have attempted, even when
// hardcoding networkId to 'loading'.
networkId: networkId === 'loading' ? 0 : parseInt(networkId, 10),
};
return Common.forCustomChain(MAINNET, customChainParams, hardfork);
}
/**
Adds a tx to the txlist
@emits ${txMeta.id}:unapproved
@ -248,6 +308,7 @@ export default class TransactionController extends EventEmitter {
*/
let txMeta = this.txStateManager.generateTxMeta({
txParams: normalizedTxParams,
origin,
});
if (origin === 'metamask') {
@ -271,8 +332,6 @@ export default class TransactionController extends EventEmitter {
}
}
txMeta.origin = origin;
const { type, getCodeResponse } = await this._determineTransactionType(
txParams,
);
@ -326,7 +385,12 @@ export default class TransactionController extends EventEmitter {
if (simulationFails) {
txMeta.simulationFails = simulationFails;
}
if (defaultGasPrice && !txMeta.txParams.gasPrice) {
if (
defaultGasPrice &&
!txMeta.txParams.gasPrice &&
!txMeta.txParams.maxPriorityFeePerGas &&
!txMeta.txParams.maxFeePerGas
) {
txMeta.txParams.gasPrice = defaultGasPrice;
}
if (defaultGasLimit && !txMeta.txParams.gas) {
@ -341,7 +405,10 @@ export default class TransactionController extends EventEmitter {
* @returns {Promise<string|undefined>} The default gas price
*/
async _getDefaultGasPrice(txMeta) {
if (txMeta.txParams.gasPrice) {
if (
txMeta.txParams.gasPrice ||
(txMeta.txParams.maxFeePerGas && txMeta.txParams.maxPriorityFeePerGas)
) {
return undefined;
}
const gasPrice = await this.query.gasPrice();
@ -572,17 +639,22 @@ export default class TransactionController extends EventEmitter {
const txMeta = this.txStateManager.getTransaction(txId);
// add network/chain id
const chainId = this.getChainId();
const txParams = { ...txMeta.txParams, chainId };
const txParams = {
...txMeta.txParams,
chainId,
gasLimit: txMeta.txParams.gas,
};
// sign tx
const fromAddress = txParams.from;
const ethTx = new Transaction(txParams);
await this.signEthTx(ethTx, fromAddress);
const common = await this.getCommonConfiguration();
const unsignedEthTx = TransactionFactory.fromTxData(txParams, { common });
const signedEthTx = await this.signEthTx(unsignedEthTx, fromAddress);
// add r,s,v values for provider request purposes see createMetamaskMiddleware
// and JSON rpc standard for further explanation
txMeta.r = bufferToHex(ethTx.r);
txMeta.s = bufferToHex(ethTx.s);
txMeta.v = bufferToHex(ethTx.v);
txMeta.r = bufferToHex(signedEthTx.r);
txMeta.s = bufferToHex(signedEthTx.s);
txMeta.v = bufferToHex(signedEthTx.v);
this.txStateManager.updateTransaction(
txMeta,
@ -591,7 +663,7 @@ export default class TransactionController extends EventEmitter {
// set state to signed
this.txStateManager.setTxStatusSigned(txMeta.id);
const rawTx = bufferToHex(ethTx.serialize());
const rawTx = bufferToHex(signedEthTx.serialize());
return rawTx;
}

@ -1,7 +1,7 @@
import { strict as assert } from 'assert';
import EventEmitter from 'events';
import { toBuffer } from 'ethereumjs-util';
import EthTx from 'ethereumjs-tx';
import { TransactionFactory } from '@ethereumjs/tx';
import { ObservableStore } from '@metamask/obs-store';
import sinon from 'sinon';
@ -20,6 +20,9 @@ import TransactionController, { TRANSACTION_EVENTS } from '.';
const noop = () => true;
const currentNetworkId = '42';
const currentChainId = '0x2a';
const providerConfig = {
type: 'kovan',
};
const VALID_ADDRESS = '0x0000000000000000000000000000000000000000';
const VALID_ADDRESS_TWO = '0x0000000000000000000000000000000000000001';
@ -36,6 +39,7 @@ describe('Transaction Controller', function () {
};
provider = createTestProviderTools({ scaffold: providerResultStub })
.provider;
fromAccount = getTestAccounts()[0];
const blockTrackerStub = new EventEmitter();
blockTrackerStub.getCurrentBlock = noop;
@ -46,13 +50,14 @@ describe('Transaction Controller', function () {
return '0xee6b2800';
},
networkStore: new ObservableStore(currentNetworkId),
getEIP1559Compatibility: () => Promise.resolve(true),
txHistoryLimit: 10,
blockTracker: blockTrackerStub,
signTransaction: (ethTx) =>
new Promise((resolve) => {
ethTx.sign(fromAccount.key);
resolve();
resolve(ethTx.sign(fromAccount.key));
}),
getProviderConfig: () => providerConfig,
getPermittedAccounts: () => undefined,
getCurrentChainId: () => currentChainId,
getParticipateInMetrics: () => false,
@ -565,8 +570,8 @@ describe('Transaction Controller', function () {
noop,
);
const rawTx = await txController.signTransaction('1');
const ethTx = new EthTx(toBuffer(rawTx));
assert.equal(ethTx.getChainId(), 42);
const ethTx = TransactionFactory.fromSerializedData(toBuffer(rawTx));
assert.equal(ethTx.common.chainIdBN().toNumber(), 42);
});
});

@ -1,17 +1,23 @@
import { ethErrors } from 'eth-rpc-errors';
import { addHexPrefix } from '../../../lib/util';
import { TRANSACTION_STATUSES } from '../../../../../shared/constants/transaction';
import {
TRANSACTION_ENVELOPE_TYPES,
TRANSACTION_STATUSES,
} from '../../../../../shared/constants/transaction';
import { isValidHexAddress } from '../../../../../shared/modules/hexstring-utils';
const normalizers = {
from: (from) => addHexPrefix(from),
from: addHexPrefix,
to: (to, lowerCase) =>
lowerCase ? addHexPrefix(to).toLowerCase() : addHexPrefix(to),
nonce: (nonce) => addHexPrefix(nonce),
value: (value) => addHexPrefix(value),
data: (data) => addHexPrefix(data),
gas: (gas) => addHexPrefix(gas),
gasPrice: (gasPrice) => addHexPrefix(gasPrice),
nonce: addHexPrefix,
value: addHexPrefix,
data: addHexPrefix,
gas: addHexPrefix,
gasPrice: addHexPrefix,
maxFeePerGas: addHexPrefix,
maxPriorityFeePerGas: addHexPrefix,
type: addHexPrefix,
};
export function normalizeAndValidateTxParams(txParams, lowerCase = true) {
@ -38,6 +44,78 @@ export function normalizeTxParams(txParams, lowerCase = true) {
return normalizedTxParams;
}
/**
* Given two fields, ensure that the second field is not included in txParams,
* and if it is throw an invalidParams error.
* @param {Object} txParams - the transaction parameters object
* @param {string} fieldBeingValidated - the current field being validated
* @param {string} mutuallyExclusiveField - the field to ensure is not provided
* @throws {ethErrors.rpc.invalidParams} - throws if mutuallyExclusiveField is
* present in txParams.
*/
function ensureMutuallyExclusiveFieldsNotProvided(
txParams,
fieldBeingValidated,
mutuallyExclusiveField,
) {
if (typeof txParams[mutuallyExclusiveField] !== 'undefined') {
throw ethErrors.rpc.invalidParams(
`Invalid transaction params: specified ${fieldBeingValidated} but also included ${mutuallyExclusiveField}, these cannot be mixed`,
);
}
}
/**
* Ensures that the provided value for field is a string, throws an
* invalidParams error if field is not a string.
* @param {Object} txParams - the transaction parameters object
* @param {string} field - the current field being validated
* @throws {ethErrors.rpc.invalidParams} - throws if field is not a string
*/
function ensureFieldIsString(txParams, field) {
if (typeof txParams[field] !== 'string') {
throw ethErrors.rpc.invalidParams(
`Invalid transaction params: ${field} is not a string. got: (${txParams[field]})`,
);
}
}
/**
* Ensures that the provided txParams has the proper 'type' specified for the
* given field, if it is provided. If types do not match throws an
* invalidParams error.
* @param {Object} txParams - the transaction parameters object
* @param {'gasPrice' | 'maxFeePerGas' | 'maxPriorityFeePerGas'} field - the
* current field being validated
* @throws {ethErrors.rpc.invalidParams} - throws if type does not match the
* expectations for provided field.
*/
function ensureProperTransactionEnvelopeTypeProvided(txParams, field) {
switch (field) {
case 'maxFeePerGas':
case 'maxPriorityFeePerGas':
if (
txParams.type &&
txParams.type !== TRANSACTION_ENVELOPE_TYPES.FEE_MARKET
) {
throw ethErrors.rpc.invalidParams(
`Invalid transaction envelope type: specified type "${txParams.type}" but including maxFeePerGas and maxPriorityFeePerGas requires type: "${TRANSACTION_ENVELOPE_TYPES.FEE_MARKET}"`,
);
}
break;
case 'gasPrice':
default:
if (
txParams.type &&
txParams.type === TRANSACTION_ENVELOPE_TYPES.FEE_MARKET
) {
throw ethErrors.rpc.invalidParams(
`Invalid transaction envelope type: specified type "${txParams.type}" but included a gasPrice instead of maxFeePerGas and maxPriorityFeePerGas`,
);
}
}
}
/**
* Validates the given tx parameters
* @param {Object} txParams - the tx params
@ -64,12 +142,43 @@ export function validateTxParams(txParams) {
case 'to':
validateRecipient(txParams);
break;
case 'gasPrice':
ensureProperTransactionEnvelopeTypeProvided(txParams, 'gasPrice');
ensureMutuallyExclusiveFieldsNotProvided(
txParams,
'gasPrice',
'maxFeePerGas',
);
ensureMutuallyExclusiveFieldsNotProvided(
txParams,
'gasPrice',
'maxPriorityFeePerGas',
);
ensureFieldIsString(txParams, 'gasPrice');
break;
case 'maxFeePerGas':
ensureProperTransactionEnvelopeTypeProvided(txParams, 'maxFeePerGas');
ensureMutuallyExclusiveFieldsNotProvided(
txParams,
'maxFeePerGas',
'gasPrice',
);
ensureFieldIsString(txParams, 'maxFeePerGas');
break;
case 'maxPriorityFeePerGas':
ensureProperTransactionEnvelopeTypeProvided(
txParams,
'maxPriorityFeePerGas',
);
ensureMutuallyExclusiveFieldsNotProvided(
txParams,
'maxPriorityFeePerGas',
'gasPrice',
);
ensureFieldIsString(txParams, 'maxPriorityFeePerGas');
break;
case 'value':
if (typeof value !== 'string') {
throw ethErrors.rpc.invalidParams(
`Invalid transaction params: ${key} is not a string. got: (${value})`,
);
}
ensureFieldIsString(txParams, 'value');
if (value.toString().includes('-')) {
throw ethErrors.rpc.invalidParams(
`Invalid transaction value "${value}": not a positive number.`,
@ -90,11 +199,7 @@ export function validateTxParams(txParams) {
}
break;
default:
if (typeof value !== 'string') {
throw ethErrors.rpc.invalidParams(
`Invalid transaction params: ${key} is not a string. got: (${value})`,
);
}
ensureFieldIsString(txParams, key);
}
});
}

@ -1,4 +1,6 @@
import { strict as assert } from 'assert';
import { TRANSACTION_ENVELOPE_TYPES } from '../../../../../shared/constants/transaction';
import { BURN_ADDRESS } from '../../../../../shared/modules/hexstring-utils';
import * as txUtils from './util';
describe('txUtils', function () {
@ -48,6 +50,239 @@ describe('txUtils', function () {
message: 'Invalid transaction value "-0x01": not a positive number.',
});
});
describe('when validating gasPrice', function () {
it('should error when specifying incorrect type', function () {
const txParams = {
gasPrice: '0x1',
type: TRANSACTION_ENVELOPE_TYPES.FEE_MARKET,
to: BURN_ADDRESS,
};
assert.throws(
() => {
txUtils.validateTxParams(txParams);
},
{
message: `Invalid transaction envelope type: specified type "0x2" but included a gasPrice instead of maxFeePerGas and maxPriorityFeePerGas`,
},
);
});
it('should error when gasPrice is not a string', function () {
const txParams = {
gasPrice: 1,
to: BURN_ADDRESS,
};
assert.throws(
() => {
txUtils.validateTxParams(txParams);
},
{
message:
'Invalid transaction params: gasPrice is not a string. got: (1)',
},
);
});
it('should error when specifying maxFeePerGas', function () {
const txParams = {
gasPrice: '0x1',
maxFeePerGas: '0x1',
to: BURN_ADDRESS,
};
assert.throws(
() => {
txUtils.validateTxParams(txParams);
},
{
message:
'Invalid transaction params: specified gasPrice but also included maxFeePerGas, these cannot be mixed',
},
);
});
it('should error when specifying maxPriorityFeePerGas', function () {
const txParams = {
gasPrice: '0x1',
maxPriorityFeePerGas: '0x1',
to: BURN_ADDRESS,
};
assert.throws(
() => {
txUtils.validateTxParams(txParams);
},
{
message:
'Invalid transaction params: specified gasPrice but also included maxPriorityFeePerGas, these cannot be mixed',
},
);
});
it('should validate if gasPrice is set with no type or EIP-1559 gas fields', function () {
const txParams = {
gasPrice: '0x1',
to: BURN_ADDRESS,
};
assert.doesNotThrow(() => txUtils.validateTxParams(txParams));
});
it('should validate if gasPrice is set with a type of "0x0"', function () {
const txParams = {
gasPrice: '0x1',
type: TRANSACTION_ENVELOPE_TYPES.LEGACY,
to: BURN_ADDRESS,
};
assert.doesNotThrow(() => txUtils.validateTxParams(txParams));
});
});
describe('when validating maxFeePerGas', function () {
it('should error when specifying incorrect type', function () {
const txParams = {
maxFeePerGas: '0x1',
type: TRANSACTION_ENVELOPE_TYPES.LEGACY,
to: BURN_ADDRESS,
};
assert.throws(
() => {
txUtils.validateTxParams(txParams);
},
{
message:
'Invalid transaction envelope type: specified type "0x0" but including maxFeePerGas and maxPriorityFeePerGas requires type: "0x2"',
},
);
});
it('should error when maxFeePerGas is not a string', function () {
const txParams = {
maxFeePerGas: 1,
to: BURN_ADDRESS,
};
assert.throws(
() => {
txUtils.validateTxParams(txParams);
},
{
message:
'Invalid transaction params: maxFeePerGas is not a string. got: (1)',
},
);
});
it('should error when specifying gasPrice', function () {
const txParams = {
gasPrice: '0x1',
maxFeePerGas: '0x1',
to: BURN_ADDRESS,
};
assert.throws(
() => {
txUtils.validateTxParams(txParams);
},
{
message:
'Invalid transaction params: specified gasPrice but also included maxFeePerGas, these cannot be mixed',
},
);
});
it('should validate if maxFeePerGas is set with no type or gasPrice field', function () {
const txParams = {
maxFeePerGas: '0x1',
to: BURN_ADDRESS,
};
assert.doesNotThrow(() => txUtils.validateTxParams(txParams));
});
it('should validate if maxFeePerGas is set with a type of "0x2"', function () {
const txParams = {
maxFeePerGas: '0x1',
type: TRANSACTION_ENVELOPE_TYPES.FEE_MARKET,
to: BURN_ADDRESS,
};
assert.doesNotThrow(() => txUtils.validateTxParams(txParams));
});
});
describe('when validating maxPriorityFeePerGas', function () {
it('should error when specifying incorrect type', function () {
const txParams = {
maxPriorityFeePerGas: '0x1',
type: TRANSACTION_ENVELOPE_TYPES.LEGACY,
to: BURN_ADDRESS,
};
assert.throws(
() => {
txUtils.validateTxParams(txParams);
},
{
message:
'Invalid transaction envelope type: specified type "0x0" but including maxFeePerGas and maxPriorityFeePerGas requires type: "0x2"',
},
);
});
it('should error when maxFeePerGas is not a string', function () {
const txParams = {
maxPriorityFeePerGas: 1,
to: BURN_ADDRESS,
};
assert.throws(
() => {
txUtils.validateTxParams(txParams);
},
{
message:
'Invalid transaction params: maxPriorityFeePerGas is not a string. got: (1)',
},
);
});
it('should error when specifying gasPrice', function () {
const txParams = {
gasPrice: '0x1',
maxPriorityFeePerGas: '0x1',
to: BURN_ADDRESS,
};
assert.throws(
() => {
txUtils.validateTxParams(txParams);
},
{
message:
'Invalid transaction params: specified gasPrice but also included maxPriorityFeePerGas, these cannot be mixed',
},
);
});
it('should validate if maxPriorityFeePerGas is set with no type or gasPrice field', function () {
const txParams = {
maxPriorityFeePerGas: '0x1',
to: BURN_ADDRESS,
};
assert.doesNotThrow(() => txUtils.validateTxParams(txParams));
});
it('should validate if maxPriorityFeePerGas is set with a type of "0x2"', function () {
const txParams = {
maxPriorityFeePerGas: '0x1',
type: TRANSACTION_ENVELOPE_TYPES.FEE_MARKET,
to: BURN_ADDRESS,
};
assert.doesNotThrow(() => txUtils.validateTxParams(txParams));
});
});
});
describe('#normalizeTxParams', function () {
@ -58,6 +293,10 @@ describe('txUtils', function () {
to: null,
data: '68656c6c6f20776f726c64',
random: 'hello world',
gasPrice: '1',
maxFeePerGas: '1',
maxPriorityFeePerGas: '1',
type: '1',
};
let normalizedTxParams = txUtils.normalizeTxParams(txParams);
@ -89,6 +328,28 @@ describe('txUtils', function () {
'0x',
'to should be hex-prefixed',
);
assert.equal(
normalizedTxParams.gasPrice,
'0x1',
'gasPrice should be hex-prefixed',
);
assert.equal(
normalizedTxParams.maxFeePerGas,
'0x1',
'maxFeePerGas should be hex-prefixed',
);
assert.equal(
normalizedTxParams.maxPriorityFeePerGas,
'0x1',
'maxPriorityFeePerGas should be hex-prefixed',
);
assert.equal(
normalizedTxParams.type,
'0x1',
'type should be hex-prefixed',
);
});
});

@ -62,8 +62,11 @@ export default class TxGasUtil {
// `eth_estimateGas` can fail if the user has insufficient balance for the
// value being sent, or for the gas cost. We don't want to check their
// balance here, we just want the gas estimate. The gas price is removed
// to skip those balance checks. We check balance elsewhere.
// to skip those balance checks. We check balance elsewhere. We also delete
// maxFeePerGas and maxPriorityFeePerGas to support EIP-1559 txs.
delete txParams.gasPrice;
delete txParams.maxFeePerGas;
delete txParams.maxPriorityFeePerGas;
// estimate tx gas requirements
return await this.query.estimateGas(txParams);

@ -1,5 +1,6 @@
import { strict as assert } from 'assert';
import Transaction from 'ethereumjs-tx';
import { TransactionFactory } from '@ethereumjs/tx';
import Common from '@ethereumjs/common';
import { hexToBn, bnToHex } from '../../lib/util';
import TxUtils from './tx-gas-utils';
@ -31,8 +32,14 @@ describe('txUtils', function () {
nonce: '0x3',
chainId: 42,
};
const ethTx = new Transaction(txParams);
assert.equal(ethTx.getChainId(), 42, 'chainId is set from tx params');
const ethTx = TransactionFactory.fromTxData(txParams, {
common: new Common({ chain: 'kovan' }),
});
assert.equal(
ethTx.common.chainIdBN().toNumber(),
42,
'chainId is set from tx params',
);
});
});

@ -72,12 +72,45 @@ export default class TransactionStateManager extends EventEmitter {
* overwriting default keys of the TransactionMeta
* @returns {TransactionMeta} the default txMeta object
*/
generateTxMeta(opts) {
generateTxMeta(opts = {}) {
const netId = this.getNetwork();
const chainId = this.getCurrentChainId();
if (netId === 'loading') {
throw new Error('MetaMask is having trouble connecting to the network');
}
let dappSuggestedGasFees = null;
// If we are dealing with a transaction suggested by a dapp and not
// an internally created metamask transaction, we need to keep record of
// the originally submitted gasParams.
if (
opts.txParams &&
typeof opts.origin === 'string' &&
opts.origin !== 'metamask'
) {
if (typeof opts.txParams.gasPrice !== 'undefined') {
dappSuggestedGasFees = {
gasPrice: opts.txParams.gasPrice,
};
} else if (
typeof opts.txParams.maxFeePerGas !== 'undefined' ||
typeof opts.txParams.maxPriorityFeePerGas !== 'undefined'
) {
dappSuggestedGasFees = {
maxPriorityFeePerGas: opts.txParams.maxPriorityFeePerGas,
maxFeePerGas: opts.txParams.maxFeePerGas,
};
}
if (typeof opts.txParams.gas !== 'undefined') {
dappSuggestedGasFees = {
...dappSuggestedGasFees,
gas: opts.txParams.gas,
};
}
}
return {
id: createId(),
time: new Date().getTime(),
@ -85,6 +118,7 @@ export default class TransactionStateManager extends EventEmitter {
metamaskNetworkId: netId,
chainId,
loadingDefaults: true,
dappSuggestedGasFees,
...opts,
};
}

@ -10,6 +10,7 @@ import {
RINKEBY_CHAIN_ID,
KOVAN_NETWORK_ID,
} from '../../../../shared/constants/network';
import { GAS_LIMITS } from '../../../../shared/constants/gas';
import TxStateManager from './tx-state-manager';
import { snapshotFromTxMeta } from './lib/tx-state-history-helpers';
@ -1074,6 +1075,136 @@ describe('TransactionStateManager', function () {
});
});
describe('#generateTxMeta', function () {
it('generates a txMeta object when supplied no parameters', function () {
// There are currently not safety checks for missing 'opts' but we should
// at least enforce txParams. This is done in the transaction controller
// before *calling* this method, but we should perhaps ensure that
// txParams is provided and validated in this method.
// TODO: this test should fail.
const generatedTransaction = txStateManager.generateTxMeta();
assert.ok(generatedTransaction);
});
it('generates a txMeta object with txParams specified', function () {
const txParams = {
gas: GAS_LIMITS.SIMPLE,
from: '0x0000',
to: '0x000',
value: '0x0',
gasPrice: '0x0',
};
const generatedTransaction = txStateManager.generateTxMeta({
txParams,
});
assert.ok(generatedTransaction);
assert.strictEqual(generatedTransaction.txParams, txParams);
});
it('generates a txMeta object with txParams specified using EIP-1559 fields', function () {
const txParams = {
gas: GAS_LIMITS.SIMPLE,
from: '0x0000',
to: '0x000',
value: '0x0',
maxFeePerGas: '0x0',
maxPriorityFeePerGas: '0x0',
};
const generatedTransaction = txStateManager.generateTxMeta({
txParams,
});
assert.ok(generatedTransaction);
assert.strictEqual(generatedTransaction.txParams, txParams);
});
it('records dappSuggestedGasFees when origin is provided and is not "metamask"', function () {
const eip1559GasFeeFields = {
maxFeePerGas: '0x0',
maxPriorityFeePerGas: '0x0',
gas: GAS_LIMITS.SIMPLE,
};
const legacyGasFeeFields = {
gasPrice: '0x0',
gas: GAS_LIMITS.SIMPLE,
};
const eip1559TxParams = {
from: '0x0000',
to: '0x000',
value: '0x0',
...eip1559GasFeeFields,
};
const legacyTxParams = {
from: '0x0000',
to: '0x000',
value: '0x0',
...legacyGasFeeFields,
};
const eip1559GeneratedTransaction = txStateManager.generateTxMeta({
txParams: eip1559TxParams,
origin: 'adappt.com',
});
const legacyGeneratedTransaction = txStateManager.generateTxMeta({
txParams: legacyTxParams,
origin: 'adappt.com',
});
assert.ok(
eip1559GeneratedTransaction,
'generated EIP1559 transaction should be truthy',
);
assert.deepStrictEqual(
eip1559GeneratedTransaction.dappSuggestedGasFees,
eip1559GasFeeFields,
'generated EIP1559 transaction should have appropriate dappSuggestedGasFees',
);
assert.ok(
legacyGeneratedTransaction,
'generated legacy transaction should be truthy',
);
assert.deepStrictEqual(
legacyGeneratedTransaction.dappSuggestedGasFees,
legacyGasFeeFields,
'generated legacy transaction should have appropriate dappSuggestedGasFees',
);
});
it('does not record dappSuggestedGasFees when transaction origin is "metamask"', function () {
const txParams = {
gas: GAS_LIMITS.SIMPLE,
from: '0x0000',
to: '0x000',
value: '0x0',
maxFeePerGas: '0x0',
maxPriorityFeePerGas: '0x0',
};
const generatedTransaction = txStateManager.generateTxMeta({
txParams,
origin: 'metamask',
});
assert.ok(generatedTransaction);
assert.strictEqual(generatedTransaction.dappSuggestedGasFees, null);
});
it('does not record dappSuggestedGasFees when transaction origin is not provided', function () {
const txParams = {
gas: GAS_LIMITS.SIMPLE,
from: '0x0000',
to: '0x000',
value: '0x0',
maxFeePerGas: '0x0',
maxPriorityFeePerGas: '0x0',
};
const generatedTransaction = txStateManager.generateTxMeta({
txParams,
});
assert.ok(generatedTransaction);
assert.strictEqual(generatedTransaction.dappSuggestedGasFees, null);
});
});
describe('#clearUnapprovedTxs', function () {
it('removes unapproved transactions', function () {
const txMetas = [

@ -1,3 +1,6 @@
import log from 'loglevel';
import { METASWAP_CHAINID_API_HOST_MAP } from '../../../shared/constants/swaps';
import {
GOERLI_CHAIN_ID,
KOVAN_CHAIN_ID,
@ -5,6 +8,54 @@ import {
RINKEBY_CHAIN_ID,
ROPSTEN_CHAIN_ID,
} from '../../../shared/constants/network';
import { SECOND } from '../../../shared/constants/time';
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout';
import { TRANSAK_API_KEY } from '../constants/on-ramp';
const fetchWithTimeout = getFetchWithTimeout(SECOND * 30);
/**
* Create a Wyre purchase URL.
* @param {String} address Ethereum destination address
* @returns String
*/
const createWyrePurchaseUrl = async (address) => {
const fiatOnRampUrlApi = `${METASWAP_CHAINID_API_HOST_MAP[MAINNET_CHAIN_ID]}/fiatOnRampUrl?serviceName=wyre&destinationAddress=${address}`;
const wyrePurchaseUrlFallback = `https://pay.sendwyre.com/purchase?dest=ethereum:${address}&destCurrency=ETH&accountId=AC-7AG3W4XH4N2&paymentMethod=debit-card`;
try {
const response = await fetchWithTimeout(fiatOnRampUrlApi, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
});
const parsedResponse = await response.json();
if (response.ok && parsedResponse.url) {
return parsedResponse.url;
}
log.warn('Failed to create a Wyre purchase URL', parsedResponse);
} catch (err) {
log.warn('Failed to create a Wyre purchase URL', err);
}
return wyrePurchaseUrlFallback; // In case the API call would fail, we return a fallback URL for Wyre's Checkout.
};
/**
* Create a Transak Checkout URL.
* API docs here: https://www.notion.so/Query-Parameters-9ec523df3b874ec58cef4fa3a906f238
* @param {String} address Ethereum destination address
* @returns String
*/
const createTransakUrl = (address) => {
const queryParams = new URLSearchParams({
apiKey: TRANSAK_API_KEY,
hostURL: 'https://metamask.io',
defaultCryptoCurrency: 'ETH',
walletAddress: address,
});
return `https://global.transak.com/?${queryParams}`;
};
/**
* Gives the caller a url at which the user can acquire eth, depending on the network they are in
@ -16,7 +67,7 @@ import {
* chainId does not match any of the specified cases, or if no chainId is given, returns undefined.
*
*/
export default function getBuyEthUrl({ chainId, address, service }) {
export default async function getBuyEthUrl({ chainId, address, service }) {
// default service by network if not specified
if (!service) {
// eslint-disable-next-line no-param-reassign
@ -25,7 +76,9 @@ export default function getBuyEthUrl({ chainId, address, service }) {
switch (service) {
case 'wyre':
return `https://pay.sendwyre.com/purchase?dest=ethereum:${address}&destCurrency=ETH&accountId=AC-7AG3W4XH4N2&paymentMethod=debit-card`;
return await createWyrePurchaseUrl(address);
case 'transak':
return createTransakUrl(address);
case 'metamask-faucet':
return 'https://faucet.metamask.io/';
case 'rinkeby-faucet':

@ -1,49 +1,76 @@
import { strict as assert } from 'assert';
import nock from 'nock';
import {
KOVAN_CHAIN_ID,
MAINNET_CHAIN_ID,
RINKEBY_CHAIN_ID,
ROPSTEN_CHAIN_ID,
} from '../../../shared/constants/network';
import { TRANSAK_API_KEY } from '../constants/on-ramp';
import getBuyEthUrl from './buy-eth-url';
const WYRE_ACCOUNT_ID = 'AC-7AG3W4XH4N2';
const ETH_ADDRESS = '0x0dcd5d886577d5581b0c524242ef2ee70be3e7bc';
const MAINNET = {
chainId: MAINNET_CHAIN_ID,
amount: 5,
address: ETH_ADDRESS,
};
const ROPSTEN = {
chainId: ROPSTEN_CHAIN_ID,
};
const RINKEBY = {
chainId: RINKEBY_CHAIN_ID,
};
const KOVAN = {
chainId: KOVAN_CHAIN_ID,
};
describe('buy-eth-url', function () {
const mainnet = {
chainId: MAINNET_CHAIN_ID,
amount: 5,
address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
};
const ropsten = {
chainId: ROPSTEN_CHAIN_ID,
};
const rinkeby = {
chainId: RINKEBY_CHAIN_ID,
};
const kovan = {
chainId: KOVAN_CHAIN_ID,
};
it('returns wyre url with address for network 1', function () {
const wyreUrl = getBuyEthUrl(mainnet);
it('returns Wyre url with an ETH address for Ethereum mainnet', async function () {
nock('https://api.metaswap.codefi.network')
.get(`/fiatOnRampUrl?serviceName=wyre&destinationAddress=${ETH_ADDRESS}`)
.reply(200, {
url: `https://pay.sendwyre.com/purchase?accountId=${WYRE_ACCOUNT_ID}&utm_campaign=${WYRE_ACCOUNT_ID}&destCurrency=ETH&utm_medium=widget&paymentMethod=debit-card&reservation=MLZVUF8FMXZUMARJC23B&dest=ethereum%3A${ETH_ADDRESS}&utm_source=checkout`,
});
const wyreUrl = await getBuyEthUrl(MAINNET);
assert.equal(
wyreUrl,
`https://pay.sendwyre.com/purchase?accountId=${WYRE_ACCOUNT_ID}&utm_campaign=${WYRE_ACCOUNT_ID}&destCurrency=ETH&utm_medium=widget&paymentMethod=debit-card&reservation=MLZVUF8FMXZUMARJC23B&dest=ethereum%3A${ETH_ADDRESS}&utm_source=checkout`,
);
nock.cleanAll();
});
it('returns a fallback Wyre url if /orders/reserve API call fails', async function () {
const wyreUrl = await getBuyEthUrl(MAINNET);
assert.equal(
wyreUrl,
'https://pay.sendwyre.com/purchase?dest=ethereum:0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc&destCurrency=ETH&accountId=AC-7AG3W4XH4N2&paymentMethod=debit-card',
`https://pay.sendwyre.com/purchase?dest=ethereum:${ETH_ADDRESS}&destCurrency=ETH&accountId=${WYRE_ACCOUNT_ID}&paymentMethod=debit-card`,
);
});
it('returns Transak url with an ETH address for Ethereum mainnet', async function () {
const transakUrl = await getBuyEthUrl({ ...MAINNET, service: 'transak' });
assert.equal(
transakUrl,
`https://global.transak.com/?apiKey=${TRANSAK_API_KEY}&hostURL=https%3A%2F%2Fmetamask.io&defaultCryptoCurrency=ETH&walletAddress=${ETH_ADDRESS}`,
);
});
it('returns metamask ropsten faucet for network 3', function () {
const ropstenUrl = getBuyEthUrl(ropsten);
it('returns metamask ropsten faucet for network 3', async function () {
const ropstenUrl = await getBuyEthUrl(ROPSTEN);
assert.equal(ropstenUrl, 'https://faucet.metamask.io/');
});
it('returns rinkeby dapp for network 4', function () {
const rinkebyUrl = getBuyEthUrl(rinkeby);
it('returns rinkeby dapp for network 4', async function () {
const rinkebyUrl = await getBuyEthUrl(RINKEBY);
assert.equal(rinkebyUrl, 'https://www.rinkeby.io/');
});
it('returns kovan github test faucet for network 42', function () {
const kovanUrl = getBuyEthUrl(kovan);
it('returns kovan github test faucet for network 42', async function () {
const kovanUrl = await getBuyEthUrl(KOVAN);
assert.equal(kovanUrl, 'https://github.com/kovan-testnet/faucet');
});
});

@ -1,5 +1,5 @@
import EventEmitter from 'events';
import assert from 'assert';
import { strict as assert } from 'assert';
import { ObservableStore } from '@metamask/obs-store';
import { ethErrors } from 'eth-rpc-errors';
import { typedSignatureHash, TYPED_MESSAGE_SCHEMA } from 'eth-sig-util';
@ -177,7 +177,7 @@ export default class TypedMessageManager extends EventEmitter {
break;
case 'V3':
case 'V4': {
assert.strictEqual(
assert.equal(
typeof params.data,
'string',
'"params.data" must be a string.',
@ -191,18 +191,21 @@ export default class TypedMessageManager extends EventEmitter {
data.primaryType in data.types,
`Primary type of "${data.primaryType}" has no type definition.`,
);
assert.strictEqual(
assert.equal(
validation.errors.length,
0,
'Signing data must conform to EIP-712 schema. See https://git.io/fNtcx.',
);
const { chainId } = data.domain;
let { chainId } = data.domain;
if (chainId) {
const activeChainId = parseInt(this._getCurrentChainId(), 16);
assert.ok(
!Number.isNaN(activeChainId),
`Cannot sign messages for chainId "${chainId}", because MetaMask is switching networks.`,
);
if (typeof chainId === 'string') {
chainId = parseInt(chainId, 16);
}
assert.equal(
chainId,
activeChainId,

@ -1,4 +1,3 @@
import { strict as assert } from 'assert';
import extension from 'extensionizer';
import { stripHexPrefix } from 'ethereumjs-util';
import BN from 'bn.js';
@ -70,39 +69,6 @@ const getPlatform = (_) => {
return PLATFORM_FIREFOX;
};
/**
* Checks whether a given balance of ETH, represented as a hex string, is sufficient to pay a value plus a gas fee
*
* @param {Object} txParams - Contains data about a transaction
* @param {string} txParams.gas - The gas for a transaction
* @param {string} txParams.gasPrice - The price per gas for the transaction
* @param {string} txParams.value - The value of ETH to send
* @param {string} hexBalance - A balance of ETH represented as a hex string
* @returns {boolean} Whether the balance is greater than or equal to the value plus the value of gas times gasPrice
*
*/
function sufficientBalance(txParams, hexBalance) {
// validate hexBalance is a hex string
assert.equal(
typeof hexBalance,
'string',
'sufficientBalance - hexBalance is not a hex string',
);
assert.equal(
hexBalance.slice(0, 2),
'0x',
'sufficientBalance - hexBalance is not a hex string',
);
const balance = hexToBn(hexBalance);
const value = hexToBn(txParams.value);
const gasLimit = hexToBn(txParams.gas);
const gasPrice = hexToBn(txParams.gasPrice);
const maxCost = value.add(gasLimit.mul(gasPrice));
return balance.gte(maxCost);
}
/**
* Converts a hex string to a BN object
*
@ -183,7 +149,6 @@ function bnToHex(inputBn) {
export {
getPlatform,
getEnvironmentType,
sufficientBalance,
hexToBn,
BnMultiplyByFraction,
checkForError,

@ -7,7 +7,7 @@ import {
ENVIRONMENT_TYPE_FULLSCREEN,
ENVIRONMENT_TYPE_BACKGROUND,
} from '../../../shared/constants/app';
import { getEnvironmentType, sufficientBalance } from './util';
import { getEnvironmentType } from './util';
describe('app utils', function () {
describe('getEnvironmentType', function () {
@ -68,44 +68,6 @@ describe('app utils', function () {
});
});
describe('SufficientBalance', function () {
it('returns true if max tx cost is equal to balance.', function () {
const tx = {
value: '0x1',
gas: '0x2',
gasPrice: '0x3',
};
const balance = '0x8';
const result = sufficientBalance(tx, balance);
assert.ok(result, 'sufficient balance found.');
});
it('returns true if max tx cost is less than balance.', function () {
const tx = {
value: '0x1',
gas: '0x2',
gasPrice: '0x3',
};
const balance = '0x9';
const result = sufficientBalance(tx, balance);
assert.ok(result, 'sufficient balance found.');
});
it('returns false if max tx cost is more than balance.', function () {
const tx = {
value: '0x1',
gas: '0x2',
gasPrice: '0x3',
};
const balance = '0x6';
const result = sufficientBalance(tx, balance);
assert.ok(!result, 'insufficient balance found.');
});
});
describe('isPrefixedFormattedHexString', function () {
it('should return true for valid hex strings', function () {
assert.equal(

@ -73,6 +73,16 @@ export const METAMASK_CONTROLLER_EVENTS = {
UPDATE_BADGE: 'updateBadge',
};
/**
* Accounts can be instantiated from simple, HD or the two hardware wallet
* keyring types. Both simple and HD are treated as default but we do special
* case accounts managed by a hardware wallet.
*/
const KEYRING_TYPES = {
LEDGER: 'Ledger Hardware',
TREZOR: 'Trezor Hardware',
};
export default class MetamaskController extends EventEmitter {
/**
* @constructor
@ -132,11 +142,17 @@ export default class MetamaskController extends EventEmitter {
this.networkController = new NetworkController(initState.NetworkController);
this.networkController.setInfuraProjectId(opts.infuraProjectId);
// now we can initialize the RPC provider, which other controllers require
this.initializeProvider();
this.provider = this.networkController.getProviderAndBlockTracker().provider;
this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker;
this.preferencesController = new PreferencesController({
initState: initState.PreferencesController,
initLangCode: opts.initLangCode,
openPopup: opts.openPopup,
network: this.networkController,
provider: this.provider,
migrateAddressBookState: this.migrateAddressBookState.bind(this),
});
@ -183,11 +199,6 @@ export default class MetamaskController extends EventEmitter {
initState.NotificationController,
);
// now we can initialize the RPC provider, which other controllers require
this.initializeProvider();
this.provider = this.networkController.getProviderAndBlockTracker().provider;
this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker;
// token exchange rate tracker
this.tokenRatesController = new TokenRatesController({
preferences: this.preferencesController.store,
@ -322,6 +333,12 @@ export default class MetamaskController extends EventEmitter {
getPermittedAccounts: this.permissionsController.getAccounts.bind(
this.permissionsController,
),
getProviderConfig: this.networkController.getProviderConfig.bind(
this.networkController,
),
getEIP1559Compatibility: this.networkController.getEIP1559Compatibility.bind(
this.networkController,
),
networkStore: this.networkController.networkStore,
getCurrentChainId: this.networkController.getCurrentChainId.bind(
this.networkController,
@ -727,6 +744,10 @@ export default class MetamaskController extends EventEmitter {
preferencesController,
),
addToken: nodeify(preferencesController.addToken, preferencesController),
updateTokenType: nodeify(
preferencesController.updateTokenType,
preferencesController,
),
removeToken: nodeify(
preferencesController.removeToken,
preferencesController,
@ -1778,7 +1799,7 @@ export default class MetamaskController extends EventEmitter {
const keyring = await this.keyringController.getKeyringForAccount(address);
switch (keyring.type) {
case 'Ledger Hardware': {
case KEYRING_TYPES.LEDGER: {
return new Promise((_, reject) => {
reject(
new Error('Ledger does not support eth_getEncryptionPublicKey.'),
@ -1786,7 +1807,7 @@ export default class MetamaskController extends EventEmitter {
});
}
case 'Trezor Hardware': {
case KEYRING_TYPES.TREZOR: {
return new Promise((_, reject) => {
reject(
new Error('Trezor does not support eth_getEncryptionPublicKey.'),
@ -1925,6 +1946,24 @@ export default class MetamaskController extends EventEmitter {
cb(null, this.getState());
}
/**
* Method to return a boolean if the keyring for the currently selected
* account is a ledger or trezor keyring.
* TODO: remove this method when Ledger and Trezor release their supported
* client utilities for EIP-1559
* @returns {boolean} true if the keyring type supports EIP-1559
*/
getCurrentAccountEIP1559Compatibility() {
const selectedAddress = this.preferencesController.getSelectedAddress();
const keyring = this.keyringController.getKeyringForAccount(
selectedAddress,
);
return (
keyring.type !== KEYRING_TYPES.LEDGER &&
keyring.type !== KEYRING_TYPES.TREZOR
);
}
//=============================================================================
// END (VAULT / KEYRING RELATED METHODS)
//=============================================================================

@ -21,6 +21,11 @@ const firstTimeState = {
rpcUrl: 'http://localhost:8545',
chainId: '0x539',
},
networkDetails: {
EIPS: {
1559: false,
},
},
},
};

@ -44,7 +44,7 @@ const materialUIDependencies = ['@material-ui/core'];
const reactDepenendencies = dependencies.filter((dep) => dep.match(/react/u));
const externalDependenciesMap = {
background: ['3box'],
background: ['3box', '@ethereumjs/common', 'unicode-confusables'],
ui: [...materialUIDependencies, ...reactDepenendencies],
};
@ -433,6 +433,9 @@ function getEnvironmentVariables({ devMode, testing }) {
PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '',
PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '',
CONF: devMode ? metamaskrc : {},
SHOW_EIP_1559_UI:
process.env.SHOW_EIP_1559_UI === '1' ||
metamaskrc.SHOW_EIP_1559_UI === '1',
SENTRY_DSN: process.env.SENTRY_DSN,
SENTRY_DSN_DEV: metamaskrc.SENTRY_DSN_DEV,
INFURA_PROJECT_ID: testing

@ -76,7 +76,7 @@ for (const tag of languageTags) {
const copyTargetsDev = [
...copyTargets,
{
src: './app/scripts/',
src: './development',
pattern: '/chromereload.js',
dest: ``,
},

@ -68,9 +68,20 @@ function runInChildProcess(task) {
);
}
return instrumentForTaskStats(taskName, async () => {
const childProcess = spawn('yarn', ['build', taskName, '--skip-stats'], {
env: process.env,
});
let childProcess;
// don't run subprocesses in lavamoat for dev mode if main process not run in lavamoat
if (
taskName.includes('scripts:core:dev') &&
!process.argv[0].includes('lavamoat')
) {
childProcess = spawn('yarn', ['build:dev', taskName, '--skip-stats'], {
env: process.env,
});
} else {
childProcess = spawn('yarn', ['build', taskName, '--skip-stats'], {
env: process.env,
});
}
// forward logs to main process
// skip the first stdout event (announcing the process command)
childProcess.stdout.once('data', () => {

@ -0,0 +1,16 @@
/**
* Exit the process with an error message.
*
* Note that this should be called before the process ends, but it will not
* itself end the process. This is because the Node.js documentation strongly
* advises against calling `process.exit` directly.
*
* @param {string} errorMessage - The error message that is causing the non-
* zero exit code.
*/
function exitWithError(errorMessage) {
console.error(errorMessage);
process.exitCode = 1;
}
module.exports = { exitWithError };

@ -0,0 +1,23 @@
/**
* Run the given function, retrying it upon failure until reaching the
* specified number of retries.
*
* @param {number} retries - The number of retries upon failure to attempt.
* @param {function} functionToRetry - The function that will be retried upon failure.
*/
async function retry(retries, functionToRetry) {
let attempts = 0;
while (attempts <= retries) {
try {
await functionToRetry();
return;
} catch (error) {
console.error(error);
} finally {
attempts += 1;
}
}
throw new Error('Retry limit reached');
}
module.exports = { retry };

@ -223,6 +223,11 @@
"js-tokens": true
}
},
"@babel/parser": {
"globals": {
"BigInt": true
}
},
"@babel/plugin-proposal-async-generator-functions": {
"packages": {
"@babel/core": true,
@ -869,6 +874,7 @@
},
"acorn": {
"globals": {
"BigInt": true,
"define": true
}
},
@ -878,6 +884,9 @@
}
},
"acorn-node": {
"globals": {
"BigInt": true
},
"packages": {
"acorn": true,
"acorn-dynamic-import": true,
@ -950,6 +959,16 @@
"buffer-equal": true
}
},
"are-we-there-yet": {
"builtin": {
"events.EventEmitter": true,
"util.inherits": true
},
"packages": {
"delegates": true,
"readable-stream": true
}
},
"arr-diff": {
"packages": {
"arr-flatten": true,
@ -1302,6 +1321,7 @@
"anymatch": true,
"async-each": true,
"braces": true,
"fsevents": true,
"glob-parent": true,
"inherits": true,
"is-binary-path": true,
@ -1553,6 +1573,16 @@
"through2": true
}
},
"detect-libc": {
"builtin": {
"child_process.spawnSync": true,
"fs.readdirSync": true,
"os.platform": true
},
"globals": {
"process.env": true
}
},
"detective": {
"packages": {
"acorn-node": true,
@ -1640,7 +1670,10 @@
"es-abstract": {
"globals": {
"AggregateError": true,
"Atomics": true,
"BigInt": true,
"FinalizationRegistry": true,
"SharedArrayBuffer": true,
"WeakRef": true
},
"packages": {
@ -1993,6 +2026,45 @@
"process.version": true
}
},
"fsevents": {
"builtin": {
"events.EventEmitter": true,
"fs.stat": true,
"path.join": true,
"util.inherits": true
},
"globals": {
"__dirname": true,
"process.nextTick": true,
"process.platform": true,
"setImmediate": true
},
"native": true,
"packages": {
"node-pre-gyp": true
}
},
"gauge": {
"builtin": {
"util.format": true
},
"globals": {
"clearInterval": true,
"process": true,
"setImmediate": true,
"setInterval": true
},
"packages": {
"aproba": true,
"console-control-strings": true,
"has-unicode": true,
"object-assign": true,
"signal-exit": true,
"string-width": true,
"strip-ansi": true,
"wide-align": true
}
},
"get-assigned-identifiers": {
"builtin": {
"assert.equal": true
@ -2373,6 +2445,16 @@
"process.argv": true
}
},
"has-unicode": {
"builtin": {
"os.type": true
},
"globals": {
"process.env.LANG": true,
"process.env.LC_ALL": true,
"process.env.LC_CTYPE": true
}
},
"has-value": {
"packages": {
"get-value": true,
@ -2526,6 +2608,11 @@
"is-plain-object": true
}
},
"is-fullwidth-code-point": {
"packages": {
"number-is-nan": true
}
},
"is-glob": {
"packages": {
"is-extglob": true
@ -2910,6 +2997,56 @@
"setTimeout": true
}
},
"node-pre-gyp": {
"builtin": {
"events.EventEmitter": true,
"fs.existsSync": true,
"fs.readFileSync": true,
"fs.renameSync": true,
"path.dirname": true,
"path.existsSync": true,
"path.join": true,
"path.resolve": true,
"url.parse": true,
"url.resolve": true,
"util.inherits": true
},
"globals": {
"__dirname": true,
"console.log": true,
"process.arch": true,
"process.cwd": true,
"process.env": true,
"process.platform": true,
"process.version.substr": true,
"process.versions": true
},
"packages": {
"detect-libc": true,
"nopt": true,
"npmlog": true,
"rimraf": true,
"semver": true
}
},
"nopt": {
"builtin": {
"path": true,
"stream.Stream": true,
"url": true
},
"globals": {
"console": true,
"process.argv": true,
"process.env.DEBUG_NOPT": true,
"process.env.NOPT_DEBUG": true,
"process.platform": true
},
"packages": {
"abbrev": true,
"osenv": true
}
},
"normalize-path": {
"packages": {
"remove-trailing-separator": true
@ -2925,6 +3062,22 @@
"once": true
}
},
"npmlog": {
"builtin": {
"events.EventEmitter": true,
"util": true
},
"globals": {
"process.nextTick": true,
"process.stderr": true
},
"packages": {
"are-we-there-yet": true,
"console-control-strings": true,
"gauge": true,
"set-blocking": true
}
},
"object-copy": {
"packages": {
"copy-descriptor": true,
@ -2937,6 +3090,7 @@
"util.inspect": true
},
"globals": {
"BigInt": true,
"HTMLElement": true
}
},
@ -2991,6 +3145,54 @@
"readable-stream": true
}
},
"os-homedir": {
"builtin": {
"os.homedir": true
},
"globals": {
"process.env": true,
"process.getuid": true,
"process.platform": true
}
},
"os-tmpdir": {
"globals": {
"process.env.SystemRoot": true,
"process.env.TEMP": true,
"process.env.TMP": true,
"process.env.TMPDIR": true,
"process.env.windir": true,
"process.platform": true
}
},
"osenv": {
"builtin": {
"child_process.exec": true,
"path": true
},
"globals": {
"process.env.COMPUTERNAME": true,
"process.env.ComSpec": true,
"process.env.EDITOR": true,
"process.env.HOSTNAME": true,
"process.env.PATH": true,
"process.env.PROMPT": true,
"process.env.PS1": true,
"process.env.Path": true,
"process.env.SHELL": true,
"process.env.USER": true,
"process.env.USERDOMAIN": true,
"process.env.USERNAME": true,
"process.env.VISUAL": true,
"process.env.path": true,
"process.nextTick": true,
"process.platform": true
},
"packages": {
"os-homedir": true,
"os-tmpdir": true
}
},
"parent-module": {
"packages": {
"callsites": true
@ -3573,6 +3775,12 @@
"process": true
}
},
"set-blocking": {
"globals": {
"process.stderr": true,
"process.stdout": true
}
},
"set-value": {
"packages": {
"extend-shallow": true,
@ -3766,6 +3974,7 @@
},
"string-width": {
"packages": {
"code-point-at": true,
"emoji-regex": true,
"is-fullwidth-code-point": true,
"strip-ansi": true
@ -4322,6 +4531,11 @@
"isexe": true
}
},
"wide-align": {
"packages": {
"string-width": true
}
},
"write": {
"builtin": {
"fs.createWriteStream": true,

@ -13,6 +13,7 @@
"start:lavamoat": "yarn build dev",
"dist": "yarn build prod",
"build": "lavamoat development/build/index.js",
"build:dev": "node development/build/index.js",
"start:test": "yarn build testDev",
"benchmark:chrome": "SELENIUM_BROWSER=chrome node test/e2e/benchmark.js",
"benchmark:firefox": "SELENIUM_BROWSER=firefox node test/e2e/benchmark.js",
@ -32,11 +33,12 @@
"test:unit:lax": "mocha --exit --require test/env.js --require test/setup.js --ignore './app/scripts/controllers/permissions/*.test.js' --recursive './app/**/*.test.js'",
"test:unit:strict": "mocha --exit --require test/env.js --require test/setup.js --recursive './app/scripts/controllers/permissions/*.test.js'",
"test:unit:path": "mocha --exit --require test/env.js --require test/setup.js --recursive",
"test:e2e:chrome": "SELENIUM_BROWSER=chrome test/e2e/run-all.sh",
"test:e2e:chrome:metrics": "SELENIUM_BROWSER=chrome mocha test/e2e/metrics.spec.js",
"test:e2e:firefox": "SELENIUM_BROWSER=firefox test/e2e/run-all.sh",
"test:e2e:firefox:metrics": "SELENIUM_BROWSER=firefox mocha test/e2e/metrics.spec.js",
"test:e2e:chrome": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js",
"test:e2e:chrome:metrics": "SELENIUM_BROWSER=chrome node test/e2e/run-e2e-test.js test/e2e/metrics.spec.js",
"test:e2e:firefox": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js",
"test:e2e:firefox:metrics": "SELENIUM_BROWSER=firefox node test/e2e/run-e2e-test.js test/e2e/metrics.spec.js",
"test:coverage": "nyc --silent --check-coverage yarn test:unit:strict && nyc --silent --no-clean yarn test:unit:lax && nyc report --reporter=text --reporter=html",
"test:e2e:single": "node test/e2e/run-e2e-test.js",
"test:coverage:jest": "jest --coverage --maxWorkers=2",
"test:coverage:strict": "nyc --check-coverage yarn test:unit:strict",
"test:coverage:path": "nyc --check-coverage yarn test:unit:path",
@ -92,13 +94,15 @@
"3box": "^1.10.2",
"@babel/runtime": "^7.5.5",
"@download/blockies": "^1.0.3",
"@ethereumjs/common": "^2.3.1",
"@ethereumjs/tx": "^3.2.1",
"@formatjs/intl-relativetimeformat": "^5.2.6",
"@fortawesome/fontawesome-free": "^5.13.0",
"@lavamoat/preinstall-always-fail": "^1.0.0",
"@material-ui/core": "^4.11.0",
"@metamask/contract-metadata": "^1.26.0",
"@metamask/controllers": "^9.0.0",
"@metamask/eth-ledger-bridge-keyring": "^0.5.0",
"@metamask/controllers": "^10.0.0",
"@metamask/eth-ledger-bridge-keyring": "^0.6.0",
"@metamask/eth-token-tracker": "^3.0.1",
"@metamask/etherscan-link": "^2.1.0",
"@metamask/jazzicon": "^2.0.0",
@ -134,10 +138,9 @@
"eth-query": "^2.1.2",
"eth-rpc-errors": "^4.0.2",
"eth-sig-util": "^3.0.0",
"eth-trezor-keyring": "^0.6.0",
"eth-trezor-keyring": "^0.7.0",
"ethereum-ens-network-map": "^1.0.2",
"ethereumjs-abi": "^0.6.4",
"ethereumjs-tx": "1.3.7",
"ethereumjs-util": "^7.0.10",
"ethereumjs-wallet": "^0.6.4",
"ethers": "^5.0.8",
@ -151,6 +154,7 @@
"fast-safe-stringify": "^2.0.7",
"fuse.js": "^3.2.0",
"globalthis": "^1.0.1",
"human-standard-collectible-abi": "^1.0.2",
"human-standard-token-abi": "^2.0.0",
"immer": "^8.0.1",
"json-rpc-engine": "^6.1.0",
@ -312,7 +316,8 @@
"vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0",
"watchify": "^3.11.1",
"webpack": "^4.41.6"
"webpack": "^4.41.6",
"yargs": "^17.0.1"
},
"engines": {
"node": "^14.15.1",

@ -0,0 +1,19 @@
diff --git a/node_modules/selenium-webdriver/chromium.js b/node_modules/selenium-webdriver/chromium.js
index d828ce5..87176f4 100644
--- a/node_modules/selenium-webdriver/chromium.js
+++ b/node_modules/selenium-webdriver/chromium.js
@@ -197,6 +197,14 @@ class ServiceBuilder extends remote.DriverService.Builder {
return this.addArguments('--log-path=' + path);
}
+ /**
+ * Enables Chrome logging.
+ * @returns {!ServiceBuilder} A self reference.
+ */
+ enableChromeLogging() {
+ return this.addArguments('--enable-chrome-logs');
+ }
+
/**
* Enables verbose logging.
* @return {!ServiceBuilder} A self reference.

@ -120,3 +120,24 @@ export const NATIVE_CURRENCY_TOKEN_IMAGE_MAP = {
};
export const INFURA_BLOCKED_KEY = 'countryBlocked';
/**
* Hardforks are points in the chain where logic is changed significantly
* enough where there is a fork and the new fork becomes the active chain.
* These constants are presented in chronological order starting with BERLIN
* because when we first needed to track the hardfork we had launched support
* for EIP-2718 (where transactions can have types and different shapes) and
* EIP-2930 (optional access lists), which were included in BERLIN.
*
* BERLIN - forked at block number 12,244,000, included typed transactions and
* optional access lists
* LONDON - future, upcoming fork that introduces the baseFeePerGas, an amount
* of the ETH transaction fees that will be burned instead of given to the
* miner. This change necessitated the third type of transaction envelope to
* specify maxFeePerGas and maxPriorityFeePerGas moving the fee bidding system
* to a second price auction model.
*/
export const HARDFORKS = {
BERLIN: 'berlin',
LONDON: 'london',
};

@ -60,6 +60,33 @@ export const TRANSACTION_TYPES = {
ETH_GET_ENCRYPTION_PUBLIC_KEY: MESSAGE_TYPE.ETH_GET_ENCRYPTION_PUBLIC_KEY,
};
/**
* In EIP-2718 typed transaction envelopes were specified, with the very first
* typed envelope being 'legacy' and describing the shape of the base
* transaction params that were hitherto the only transaction type sent on
* Ethereum.
* @typedef {Object} TransactionEnvelopeTypes
* @property {'0x0'} LEGACY - A legacy transaction, the very first type.
* @property {'0x1'} ACCESS_LIST - EIP-2930 defined the access list transaction
* type that allowed for specifying the state that a transaction would act
* upon in advance and theoretically save on gas fees.
* @property {'0x2'} FEE_MARKET - The type introduced comes from EIP-1559,
* Fee Market describes the addition of a baseFee to blocks that will be
* burned instead of distributed to miners. Transactions of this type have
* both a maxFeePerGas (maximum total amount in gwei per gas to spend on the
* transaction) which is inclusive of the maxPriorityFeePerGas (maximum amount
* of gwei per gas from the transaction fee to distribute to miner).
*/
/**
* @type {TransactionEnvelopeTypes}
*/
export const TRANSACTION_ENVELOPE_TYPES = {
LEGACY: '0x0',
ACCESS_LIST: '0x1',
FEE_MARKET: '0x2',
};
/**
* Transaction Status is a mix of Ethereum and MetaMask terminology, used internally
* for transaction processing.

@ -1,4 +1,3 @@
import { strict as assert } from 'assert';
import BN from 'bn.js';
import { toBuffer } from './buffer-utils';
@ -6,64 +5,64 @@ describe('buffer utils', function () {
describe('toBuffer', function () {
it('should work with prefixed hex strings', function () {
const result = toBuffer('0xe');
assert.equal(result.length, 1);
expect(result).toHaveLength(1);
});
it('should work with non prefixed hex strings', function () {
const result = toBuffer('e');
assert.equal(result.length, 1);
expect(result).toHaveLength(1);
});
it('should work with weirdly 0x prefixed non-hex strings', function () {
const result = toBuffer('0xtest');
assert.equal(result.length, 6);
expect(result).toHaveLength(6);
});
it('should work with regular strings', function () {
const result = toBuffer('test');
assert.equal(result.length, 4);
expect(result).toHaveLength(4);
});
it('should work with BN', function () {
const result = toBuffer(new BN(100));
assert.equal(result.length, 1);
expect(result).toHaveLength(1);
});
it('should work with Buffer', function () {
const result = toBuffer(Buffer.from('test'));
assert.equal(result.length, 4);
expect(result).toHaveLength(4);
});
it('should work with a number', function () {
const result = toBuffer(100);
assert.equal(result.length, 1);
expect(result).toHaveLength(1);
});
it('should work with null or undefined', function () {
const result = toBuffer(null);
const result2 = toBuffer(undefined);
assert.equal(result.length, 0);
assert.equal(result2.length, 0);
expect(result).toHaveLength(0);
expect(result2).toHaveLength(0);
});
it('should work with UInt8Array', function () {
const uint8 = new Uint8Array(2);
const result = toBuffer(uint8);
assert.equal(result.length, 2);
expect(result).toHaveLength(2);
});
it('should work with objects that have a toBuffer property', function () {
const result = toBuffer({
toBuffer: () => Buffer.from('hi'),
});
assert.equal(result.length, 2);
expect(result).toHaveLength(2);
});
it('should work with objects that have a toArray property', function () {
const result = toBuffer({
toArray: () => ['hi'],
});
assert.equal(result.length, 1);
expect(result).toHaveLength(1);
});
});
});

@ -1,6 +1,4 @@
import { strict as assert } from 'assert';
import nock from 'nock';
import { MILLISECOND, SECOND } from '../constants/time';
import getFetchWithTimeout from './fetch-with-timeout';
@ -12,7 +10,7 @@ describe('getFetchWithTimeout', function () {
const response = await (
await fetchWithTimeout('https://api.infura.io/money')
).json();
assert.deepEqual(response, {
expect(response).toStrictEqual({
hodl: false,
});
});
@ -25,14 +23,14 @@ describe('getFetchWithTimeout', function () {
const fetchWithTimeout = getFetchWithTimeout(MILLISECOND * 123);
try {
const fetchWithTimeoutThrowsError = async () => {
await fetchWithTimeout('https://api.infura.io/moon').then((r) =>
r.json(),
);
assert.fail('Request should throw');
} catch (e) {
assert.ok(e);
}
throw new Error('Request should throw');
};
await expect(fetchWithTimeoutThrowsError()).rejects.toThrow('Aborted');
});
it('should abort the request when the custom timeout is hit', async function () {
@ -43,20 +41,28 @@ describe('getFetchWithTimeout', function () {
const fetchWithTimeout = getFetchWithTimeout(MILLISECOND * 123);
try {
const fetchWithTimeoutThrowsError = async () => {
await fetchWithTimeout('https://api.infura.io/moon').then((r) =>
r.json(),
);
assert.fail('Request should be aborted');
} catch (e) {
assert.deepEqual(e.message, 'Aborted');
}
throw new Error('Request should be aborted');
};
await expect(fetchWithTimeoutThrowsError()).rejects.toThrow('Aborted');
});
it('throws on invalid timeout', async function () {
assert.throws(() => getFetchWithTimeout(), 'should throw');
assert.throws(() => getFetchWithTimeout(-1), 'should throw');
assert.throws(() => getFetchWithTimeout({}), 'should throw');
assert.throws(() => getFetchWithTimeout(true), 'should throw');
expect(() => getFetchWithTimeout()).toThrow(
'Must specify positive integer timeout.',
);
expect(() => getFetchWithTimeout(-1)).toThrow(
'Must specify positive integer timeout.',
);
expect(() => getFetchWithTimeout({})).toThrow(
'Must specify positive integer timeout.',
);
expect(() => getFetchWithTimeout(true)).toThrow(
'Must specify positive integer timeout.',
);
});
});

@ -1,4 +1,3 @@
import { strict as assert } from 'assert';
import { toChecksumAddress } from 'ethereumjs-util';
import { isValidHexAddress } from './hexstring-utils';
@ -7,51 +6,51 @@ describe('hexstring utils', function () {
it('should allow 40-char non-prefixed hex', function () {
const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b825';
const result = isValidHexAddress(address);
assert.equal(result, true);
expect(result).toBe(true);
});
it('should allow 42-char prefixed hex', function () {
const address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825';
const result = isValidHexAddress(address);
assert.equal(result, true);
expect(result).toBe(true);
});
it('should NOT allow 40-char non-prefixed hex when allowNonPrefixed is false', function () {
const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b825';
const result = isValidHexAddress(address, { allowNonPrefixed: false });
assert.equal(result, false);
expect(result).toBe(false);
});
it('should NOT allow any length of non hex-prefixed string', function () {
const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b85';
const result = isValidHexAddress(address);
assert.equal(result, false);
expect(result).toBe(false);
});
it('should NOT allow less than 42 character hex-prefixed string', function () {
const address = '0xfdea65ce26263f6d9a1b5de9555d2931a33b85';
const result = isValidHexAddress(address);
assert.equal(result, false);
expect(result).toBe(false);
});
it('should recognize correct capitalized checksum', function () {
const address = '0xFDEa65C8e26263F6d9A1B5de9555D2931A33b825';
const result = isValidHexAddress(address, { mixedCaseUseChecksum: true });
assert.equal(result, true);
expect(result).toBe(true);
});
it('should recognize incorrect capitalized checksum', function () {
const address = '0xFDea65C8e26263F6d9A1B5de9555D2931A33b825';
const result = isValidHexAddress(address, { mixedCaseUseChecksum: true });
assert.equal(result, false);
expect(result).toBe(false);
});
it('should recognize this sample hashed address', function () {
const address = '0x5Fda30Bb72B8Dfe20e48A00dFc108d0915BE9Bb0';
const result = isValidHexAddress(address, { mixedCaseUseChecksum: true });
const hashed = toChecksumAddress(address.toLowerCase());
assert.equal(hashed, address);
assert.equal(result, true);
expect(hashed).toBe(address);
expect(result).toBe(true);
});
});
});

@ -2,7 +2,11 @@
const path = require('path');
const { promises: fs, constants: fsConstants } = require('fs');
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
const ttest = require('ttest');
const { retry } = require('../../development/lib/retry');
const { exitWithError } = require('../../development/lib/exit-with-error');
const { withFixtures } = require('./helpers');
const { PAGES } = require('./webdriver/driver');
@ -38,6 +42,9 @@ const minResult = calculateResult((array) => Math.min(...array));
const maxResult = calculateResult((array) => Math.max(...array));
const averageResult = calculateResult((array) => calculateAverage(array));
const standardDeviationResult = calculateResult((array) => {
if (array.length === 1) {
return 0;
}
const average = calculateAverage(array);
const squareDiffs = array.map((value) => Math.pow(value - average, 2));
return Math.sqrt(calculateAverage(squareDiffs));
@ -46,15 +53,19 @@ const standardDeviationResult = calculateResult((array) => {
const calculateMarginOfError = (array) =>
ttest(array).confidence()[1] - calculateAverage(array);
const marginOfErrorResult = calculateResult((array) =>
calculateMarginOfError(array),
array.length === 1 ? 0 : calculateMarginOfError(array),
);
async function profilePageLoad(pages, numSamples) {
async function profilePageLoad(pages, numSamples, retries) {
const results = {};
for (const pageName of pages) {
const runResults = [];
for (let i = 0; i < numSamples; i += 1) {
runResults.push(await measurePage(pageName));
let result;
await retry(retries, async () => {
result = await measurePage(pageName);
});
runResults.push(result);
}
if (runResults.some((result) => result.navigation.lenth > 1)) {
@ -126,66 +137,63 @@ async function getFirstParentDirectoryThatExists(directory) {
}
async function main() {
const args = process.argv.slice(2);
const { argv } = yargs(hideBin(process.argv)).usage(
'$0 [options]',
'Run a page load benchmark',
(_yargs) =>
_yargs
.option('pages', {
array: true,
default: ['home'],
description:
'Set the page(s) to be benchmarked. This flag can accept multiple values (space-separated).',
choices: ALL_PAGES,
})
.option('samples', {
default: DEFAULT_NUM_SAMPLES,
description: 'The number of times the benchmark should be run.',
type: 'number',
})
.option('out', {
description:
'Output filename. Output printed to STDOUT of this is omitted.',
type: 'string',
normalize: true,
})
.option('retries', {
default: 0,
description:
'Set how many times each benchmark sample should be retried upon failure.',
type: 'number',
}),
);
const { pages, samples, out, retries } = argv;
let pages = ['home'];
let numSamples = DEFAULT_NUM_SAMPLES;
let outputPath;
let outputDirectory;
let existingParentDirectory;
while (args.length) {
if (/^(--pages|-p)$/u.test(args[0])) {
if (args[1] === undefined) {
throw new Error('Missing pages argument');
}
pages = args[1].split(',');
for (const page of pages) {
if (!ALL_PAGES.includes(page)) {
throw new Error(`Invalid page: '${page}`);
}
}
args.splice(0, 2);
} else if (/^(--samples|-s)$/u.test(args[0])) {
if (args[1] === undefined) {
throw new Error('Missing number of samples');
}
numSamples = parseInt(args[1], 10);
if (isNaN(numSamples)) {
throw new Error(`Invalid 'samples' argument given: '${args[1]}'`);
}
args.splice(0, 2);
} else if (/^(--out|-o)$/u.test(args[0])) {
if (args[1] === undefined) {
throw new Error('Missing output filename');
}
outputPath = path.resolve(args[1]);
outputDirectory = path.dirname(outputPath);
existingParentDirectory = await getFirstParentDirectoryThatExists(
outputDirectory,
);
if (!(await isWritable(existingParentDirectory))) {
throw new Error(`Specified directory is not writable: '${args[1]}'`);
}
args.splice(0, 2);
} else {
throw new Error(`Unrecognized argument: '${args[0]}'`);
if (out) {
outputDirectory = path.dirname(out);
existingParentDirectory = await getFirstParentDirectoryThatExists(
outputDirectory,
);
if (!(await isWritable(existingParentDirectory))) {
throw new Error('Specified output file directory is not writable');
}
}
const results = await profilePageLoad(pages, numSamples);
const results = await profilePageLoad(pages, samples, retries);
if (outputPath) {
if (out) {
if (outputDirectory !== existingParentDirectory) {
await fs.mkdir(outputDirectory, { recursive: true });
}
await fs.writeFile(outputPath, JSON.stringify(results, null, 2));
await fs.writeFile(out, JSON.stringify(results, null, 2));
} else {
console.log(JSON.stringify(results, null, 2));
}
}
main().catch((e) => {
console.error(e);
process.exit(1);
main().catch((error) => {
exitWithError(error);
});

@ -33,6 +33,7 @@ async function withFixtures(options, testSuite) {
let segmentStub;
let webDriver;
let failed = false;
try {
await ganacheServer.start(ganacheOptions);
if (ganacheOptions?.concurrent) {
@ -103,6 +104,7 @@ async function withFixtures(options, testSuite) {
}
}
} catch (error) {
failed = true;
if (webDriver) {
try {
await webDriver.verboseReportOnFailure(title);
@ -112,26 +114,28 @@ async function withFixtures(options, testSuite) {
}
throw error;
} finally {
await fixtureServer.stop();
await ganacheServer.quit();
if (ganacheOptions?.concurrent) {
await secondaryGanacheServer.quit();
}
if (webDriver) {
await webDriver.quit();
}
if (dappServer) {
await new Promise((resolve, reject) => {
dappServer.close((error) => {
if (error) {
return reject(error);
}
return resolve();
if (!failed || process.env.E2E_LEAVE_RUNNING !== 'true') {
await fixtureServer.stop();
await ganacheServer.quit();
if (ganacheOptions?.concurrent) {
await secondaryGanacheServer.quit();
}
if (webDriver) {
await webDriver.quit();
}
if (dappServer) {
await new Promise((resolve, reject) => {
dappServer.close((error) => {
if (error) {
return reject(error);
}
return resolve();
});
});
});
}
if (segmentServer) {
await segmentServer.stop();
}
if (segmentServer) {
await segmentServer.stop();
}
}
}
}

@ -1,24 +1,44 @@
const { strict: assert } = require('assert');
const path = require('path');
const enLocaleMessages = require('../../app/_locales/en/messages.json');
const createStaticServer = require('../../development/create-static-server');
const { tinyDelayMs, regularDelayMs, largeDelayMs } = require('./helpers');
const { buildWebDriver } = require('./webdriver');
const Ganache = require('./ganache');
const ganacheServer = new Ganache();
const dappPort = 8080;
describe('MetaMask', function () {
let driver;
let dappServer;
let tokenAddress;
const testSeedPhrase =
'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent';
this.timeout(0);
this.bail(true);
let failed = false;
before(async function () {
await ganacheServer.start();
const dappDirectory = path.resolve(
__dirname,
'..',
'..',
'node_modules',
'@metamask',
'test-dapp',
'dist',
);
dappServer = createStaticServer(dappDirectory);
dappServer.listen(dappPort);
await new Promise((resolve, reject) => {
dappServer.on('listening', resolve);
dappServer.on('error', reject);
});
const result = await buildWebDriver();
driver = result.driver;
await driver.navigate();
@ -36,13 +56,25 @@ describe('MetaMask', function () {
}
}
if (this.currentTest.state === 'failed') {
failed = true;
await driver.verboseReportOnFailure(this.currentTest.title);
}
});
after(async function () {
if (process.env.E2E_LEAVE_RUNNING === 'true' && failed) {
return;
}
await ganacheServer.quit();
await driver.quit();
await new Promise((resolve, reject) => {
dappServer.close((error) => {
if (error) {
return reject(error);
}
return resolve();
});
});
});
describe('Going through the first time flow', function () {
@ -174,50 +206,6 @@ describe('MetaMask', function () {
});
});
describe('Lock an unlock', function () {
it('logs out of the account', async function () {
await driver.clickElement('.account-menu__icon');
await driver.delay(regularDelayMs);
const lockButton = await driver.findClickableElement(
'.account-menu__lock-button',
);
assert.equal(await lockButton.getText(), 'Lock');
await lockButton.click();
await driver.delay(regularDelayMs);
});
it('accepts the account password after lock', async function () {
await driver.fill('#password', 'correct horse battery staple');
await driver.press('#password', driver.Key.ENTER);
await driver.delay(largeDelayMs * 4);
});
});
describe('Add account', function () {
it('choose Create Account from the account menu', async function () {
await driver.clickElement('.account-menu__icon');
await driver.delay(regularDelayMs);
await driver.clickElement({ text: 'Create Account', tag: 'div' });
await driver.delay(regularDelayMs);
});
it('set account name', async function () {
await driver.fill('.new-account-create-form input', '2nd account');
await driver.delay(regularDelayMs);
await driver.clickElement({ text: 'Create', tag: 'button' });
await driver.delay(largeDelayMs);
});
it('should display correct account name', async function () {
const accountName = await driver.findElement('.selected-account__name');
assert.equal(await accountName.getText(), '2nd account');
await driver.delay(regularDelayMs);
});
});
describe('Import Secret Recovery Phrase', function () {
it('logs out of the vault', async function () {
await driver.clickElement('.account-menu__icon');
@ -265,184 +253,6 @@ describe('MetaMask', function () {
});
});
describe('Send ETH from inside MetaMask using default gas', function () {
it('starts a send transaction', async function () {
await driver.clickElement('[data-testid="eth-overview-send"]');
await driver.delay(regularDelayMs);
await driver.fill(
'input[placeholder="Search, public address (0x), or ENS"]',
'0x2f318C334780961FB129D2a6c30D0763d9a5C970',
);
const inputAmount = await driver.findElement('.unit-input__input');
await inputAmount.fill('1000');
const errorAmount = await driver.findElement('.send-v2__error-amount');
assert.equal(
await errorAmount.getText(),
'Insufficient funds.',
'send screen should render an insufficient fund error message',
);
await inputAmount.press(driver.Key.BACK_SPACE);
await driver.delay(50);
await inputAmount.press(driver.Key.BACK_SPACE);
await driver.delay(50);
await inputAmount.press(driver.Key.BACK_SPACE);
await driver.delay(tinyDelayMs);
await driver.assertElementNotPresent('.send-v2__error-amount');
const amountMax = await driver.findClickableElement(
'.send-v2__amount-max',
);
await amountMax.click();
let inputValue = await inputAmount.getAttribute('value');
assert(Number(inputValue) > 99);
await amountMax.click();
assert.equal(await inputAmount.isEnabled(), true);
await inputAmount.fill('1');
inputValue = await inputAmount.getAttribute('value');
assert.equal(inputValue, '1');
await driver.delay(regularDelayMs);
// Continue to next screen
await driver.clickElement({ text: 'Next', tag: 'button' });
await driver.delay(regularDelayMs);
});
it('confirms the transaction', async function () {
await driver.clickElement({ text: 'Confirm', tag: 'button' });
await driver.delay(largeDelayMs * 2);
});
it('finds the transaction in the transactions list', async function () {
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);
await driver.waitForSelector({
css: '.transaction-list-item__primary-currency',
text: '-1 ETH',
});
});
});
describe('Send ETH from inside MetaMask using fast gas option', function () {
it('starts a send transaction', async function () {
await driver.clickElement('[data-testid="eth-overview-send"]');
await driver.delay(regularDelayMs);
await driver.fill(
'input[placeholder="Search, public address (0x), or ENS"]',
'0x2f318C334780961FB129D2a6c30D0763d9a5C970',
);
const inputAmount = await driver.findElement('.unit-input__input');
await inputAmount.fill('1');
const inputValue = await inputAmount.getAttribute('value');
assert.equal(inputValue, '1');
// Set the gas price
await driver.clickElement({ text: 'Fast', tag: 'button/div/div' });
await driver.delay(regularDelayMs);
// Continue to next screen
await driver.clickElement({ text: 'Next', tag: 'button' });
await driver.delay(regularDelayMs);
});
it('confirms the transaction', async function () {
await driver.clickElement({ text: 'Confirm', tag: 'button' });
await driver.delay(largeDelayMs);
});
it('finds the transaction in the transactions list', async function () {
await driver.waitForSelector(
'.transaction-list__completed-transactions .transaction-list-item:nth-child(2)',
);
await driver.waitForSelector({
css: '.transaction-list-item__primary-currency',
text: '-1 ETH',
});
});
});
describe('Send ETH from inside MetaMask using advanced gas modal', function () {
it('starts a send transaction', async function () {
await driver.clickElement('[data-testid="eth-overview-send"]');
await driver.delay(regularDelayMs);
await driver.fill(
'input[placeholder="Search, public address (0x), or ENS"]',
'0x2f318C334780961FB129D2a6c30D0763d9a5C970',
);
const inputAmount = await driver.findElement('.unit-input__input');
await inputAmount.fill('1');
const inputValue = await inputAmount.getAttribute('value');
assert.equal(inputValue, '1');
// Set the gas limit
await driver.clickElement('.advanced-gas-options-btn');
await driver.delay(regularDelayMs);
// wait for gas modal to be visible
const gasModal = await driver.findVisibleElement('span .modal');
await driver.clickElement({ text: 'Save', tag: 'button' });
// Wait for gas modal to be removed from DOM
await gasModal.waitForElementState('hidden');
await driver.delay(regularDelayMs);
// Continue to next screen
await driver.clickElement({ text: 'Next', tag: 'button' });
await driver.delay(regularDelayMs);
});
it('confirms the transaction', async function () {
const transactionAmounts = await driver.findElements(
'.currency-display-component__text',
);
const transactionAmount = transactionAmounts[0];
assert.equal(await transactionAmount.getText(), '1');
await driver.clickElement({ text: 'Confirm', tag: 'button' });
await driver.delay(largeDelayMs);
});
it('finds the transaction in the transactions list', async function () {
await driver.wait(async () => {
const confirmedTxes = await driver.findElements(
'.transaction-list__completed-transactions .transaction-list-item',
);
return confirmedTxes.length === 3;
}, 10000);
await driver.waitForSelector(
{
css: '.transaction-list-item__primary-currency',
text: '-1 ETH',
},
{ timeout: 10000 },
);
});
});
describe('Send ETH from dapp using advanced gas controls', function () {
let windowHandles;
let extension;
@ -553,11 +363,12 @@ describe('MetaMask', function () {
});
it('finds the transaction in the transactions list', async function () {
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 === 4;
return confirmedTxes.length === 1;
}, 10000);
await driver.waitForSelector({
@ -772,7 +583,7 @@ describe('MetaMask', function () {
const confirmedTxes = await driver.findElements(
'.transaction-list__completed-transactions .transaction-list-item',
);
return confirmedTxes.length === 5;
return confirmedTxes.length === 2;
}, 10000);
});
});
@ -823,7 +634,7 @@ describe('MetaMask', function () {
await driver.delay(largeDelayMs);
await driver.waitForSelector(
'.transaction-list__completed-transactions .transaction-list-item:nth-of-type(6)',
'.transaction-list__completed-transactions .transaction-list-item:nth-of-type(3)',
);
await driver.waitForSelector(
@ -905,7 +716,7 @@ describe('MetaMask', function () {
await driver.delay(regularDelayMs);
await driver.waitForSelector(
'.transaction-list__completed-transactions .transaction-list-item:nth-of-type(7)',
'.transaction-list__completed-transactions .transaction-list-item:nth-of-type(4)',
{ timeout: 10000 },
);
await driver.waitForSelector(
@ -940,7 +751,7 @@ describe('MetaMask', function () {
const confirmedTxes = await driver.findElements(
'.transaction-list__completed-transactions .transaction-list-item',
);
return confirmedTxes.length === 8;
return confirmedTxes.length === 5;
}, 10000);
await driver.waitForSelector(
@ -959,12 +770,12 @@ describe('MetaMask', function () {
const balance = await driver.waitForSelector(
{
css: '[data-testid="eth-overview__primary-currency"]',
text: '87.',
text: '90.',
},
{ timeout: 10000 },
);
const tokenAmount = await balance.getText();
assert.ok(/^87.*\s*ETH.*$/u.test(tokenAmount));
assert.ok(/^90.*\s*ETH.*$/u.test(tokenAmount));
await driver.delay(regularDelayMs);
});
});

@ -0,0 +1,57 @@
const path = require('path');
const { promises: fs } = require('fs');
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
const { runInShell } = require('../../development/lib/run-command');
const { exitWithError } = require('../../development/lib/exit-with-error');
async function main() {
const { argv } = yargs(hideBin(process.argv))
.usage(
'$0 [options]',
'Run all E2E tests, with a variable number of retries.',
(_yargs) =>
_yargs
.option('browser', {
description: `Set the browser used; either 'chrome' or 'firefox'.`,
type: 'string',
choices: ['chrome', 'firefox'],
})
.option('retries', {
description:
'Set how many times the test should be retried upon failure.',
type: 'number',
}),
)
.strict()
.help('help');
const { browser, retries } = argv;
const testDir = path.join(__dirname, 'tests');
const metamaskUiTest = path.join(__dirname, 'metamask-ui.spec.js');
const testFilenames = await fs.readdir(testDir);
const testPaths = testFilenames.map((filename) =>
path.join(testDir, filename),
);
const allE2eTestPaths = [...testPaths, metamaskUiTest];
const runE2eTestPath = path.join(__dirname, 'run-e2e-test.js');
const args = [runE2eTestPath];
if (browser) {
args.push('--browser', browser);
}
if (retries) {
args.push('--retries', retries);
}
for (const testPath of allE2eTestPaths) {
await runInShell('node', [...args, testPath]);
}
}
main().catch((error) => {
exitWithError(error);
});

@ -5,33 +5,11 @@ set -e
set -u
set -o pipefail
retry () {
retry=0
limit="${METAMASK_E2E_RETRY_LIMIT:-3}"
while [[ $retry -lt $limit ]]
do
"$@" && break
retry=$(( retry + 1 ))
sleep 1
done
readonly __DIR__=$( cd "${BASH_SOURCE[0]%/*}" && pwd )
if [[ $retry == "$limit" ]]
then
exit 1
fi
}
export PATH="$PATH:./node_modules/.bin"
for spec in test/e2e/tests/*.spec.js
for spec in "${__DIR__}"/tests/*.spec.js
do
retry mocha --no-timeouts "${spec}"
node "${__DIR__}/run-e2e-test.js" "${spec}"
done
retry concurrently --kill-others \
--names 'dapp,e2e' \
--prefix '[{time}][{name}]' \
--success first \
'yarn dapp' \
'mocha test/e2e/metamask-ui.spec'
node "${__DIR__}/run-e2e-test.js" "${__DIR__}/metamask-ui.spec.js"

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

Loading…
Cancel
Save