mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
fix merge conflicts
This commit is contained in:
commit
c651212025
@ -47,14 +47,14 @@ workflows:
|
||||
requires:
|
||||
- prep-deps-npm
|
||||
- prep-build
|
||||
- test-integration-mascara-chrome:
|
||||
requires:
|
||||
- prep-deps-npm
|
||||
- prep-scss
|
||||
- test-integration-mascara-firefox:
|
||||
requires:
|
||||
- prep-deps-npm
|
||||
- prep-scss
|
||||
# - test-integration-mascara-chrome:
|
||||
# requires:
|
||||
# - prep-deps-npm
|
||||
# - prep-scss
|
||||
# - test-integration-mascara-firefox:
|
||||
# requires:
|
||||
# - prep-deps-npm
|
||||
# - prep-scss
|
||||
- test-integration-flat-chrome:
|
||||
requires:
|
||||
- prep-deps-npm
|
||||
@ -73,8 +73,8 @@ workflows:
|
||||
- test-e2e-beta-chrome
|
||||
- test-e2e-beta-firefox
|
||||
- test-e2e-beta-drizzle
|
||||
- test-integration-mascara-chrome
|
||||
- test-integration-mascara-firefox
|
||||
# - test-integration-mascara-chrome
|
||||
# - test-integration-mascara-firefox
|
||||
- test-integration-flat-chrome
|
||||
- test-integration-flat-firefox
|
||||
- job-screens:
|
||||
@ -293,9 +293,9 @@ jobs:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- store_artifacts:
|
||||
path: dist/mascara
|
||||
destination: builds/mascara
|
||||
# - store_artifacts:
|
||||
# path: dist/mascara
|
||||
# destination: builds/mascara
|
||||
- store_artifacts:
|
||||
path: dist/sourcemaps
|
||||
destination: builds/sourcemaps
|
||||
@ -322,9 +322,9 @@ jobs:
|
||||
- run:
|
||||
name: github gh-pages docs publish
|
||||
command: >
|
||||
git config user.name metamaskbot &&
|
||||
git config user.email admin@metamask.io &&
|
||||
gh-pages -d docs/jsdocs
|
||||
git config --global user.name "metamaskbot" &&
|
||||
git config --global user.email "admin@metamask.io" &&
|
||||
npm run publish-docs
|
||||
|
||||
test-unit:
|
||||
docker:
|
||||
|
20
CHANGELOG.md
20
CHANGELOG.md
@ -2,6 +2,26 @@
|
||||
|
||||
## Current Develop Branch
|
||||
|
||||
## 5.0.0 Tuesday November 6 2018
|
||||
|
||||
- Implements EIP 1102 as a user-activated "Privacy Mode".
|
||||
|
||||
## 4.17.1 Saturday November 3 2018
|
||||
|
||||
- Revert chain ID lookup change which introduced a bug which caused problems when connecting to mainnet via Infura's RESTful API.
|
||||
|
||||
## 4.17.0 Thursday November 1 2018
|
||||
|
||||
- Fix bug where data lookups like balances would get stale data (stopped block-tracker bug)
|
||||
- Transaction Details now show entry for onchain failure
|
||||
- [#5559](https://github.com/MetaMask/metamask-extension/pull/5559) Localize language names in translation select list
|
||||
- [#5283](https://github.com/MetaMask/metamask-extension/pull/5283): Fix bug when eth.getCode() called with no contract
|
||||
- [#5563](https://github.com/MetaMask/metamask-extension/pull/5563#pullrequestreview-166769174) Feature: improve Hatian Creole translations
|
||||
- Feature: improve Slovenian translations
|
||||
- Add support for alternate `wallet_watchAsset` rpc method name
|
||||
- Attempt chain ID lookup via `eth_chainId` before `net_version`
|
||||
- Fix account display width for large currency values
|
||||
|
||||
## 4.16.0 Wednesday October 17 2018
|
||||
|
||||
- Feature: Add toggle for primary currency (eth/fiat)
|
||||
|
@ -1,4 +1,49 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Režim súkromia"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Webové stránky musia požiadať o prístup k zobrazeniu informácií o vašom účte."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Vystavte účty"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "Vystavte účty na aktuální webové stránky. Užitečné pro starší dappy."
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "Opravdu chcete své účty vystavit na stávajícím webu?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Naozaj chcete vymazať schválené webové stránky?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Schválené údaje webových stránek byly úspěšně zrušeny."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Údaje o schválení"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Vymažte schválené údaje webových stránek, aby všechny weby znovu požádaly o schválení."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Jasné údaje o schválení"
|
||||
},
|
||||
"approve": {
|
||||
"message": "Schválit"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Odmítnout"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "Požadavek API Ethereum"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "Přečtěte si prosím tuto žádost API Ethereum."
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Níže uvedená doména se pokouší požádat o přístup k API Ethereum, aby mohla komunikovat s blokádou Ethereum. Před schválením přístupu Ethereum vždy zkontrolujte, zda jste na správném místě."
|
||||
},
|
||||
"accept": {
|
||||
"message": "Přijmout"
|
||||
},
|
||||
|
@ -1,4 +1,49 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Datenschutzmodus"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Websites müssen Zugriff anfordern, um Ihre Kontoinformationen anzuzeigen."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Expose Konten"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "Expose Konten auf der aktuellen Website. Nützlich für ältere Dapps."
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "Möchten Sie Ihre Konten wirklich der aktuellen Website zugänglich machen?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Möchten Sie die genehmigten Websites wirklich löschen?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Genehmigte Website-Daten wurden erfolgreich gelöscht."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Genehmigungsdaten"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Löschen Sie die genehmigten Website-Daten, damit alle Websites erneut eine Genehmigung anfordern müssen."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Genehmigungsdaten löschen"
|
||||
},
|
||||
"approve": {
|
||||
"message": "Genehmigen"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Ablehnen"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "Web3-API-Anfrage"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "Bitte lesen Sie diese Ethereum-API-Anfrage."
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Die unten aufgeführte Domäne versucht, Zugriff auf die Ethereum-API anzufordern, damit sie mit der Ethereum-Blockchain interagieren kann. Überprüfen Sie immer, dass Sie sich auf der richtigen Website befinden, bevor Sie den Web3-Zugriff genehmigen."
|
||||
},
|
||||
"accept": {
|
||||
"message": "Annehmen"
|
||||
},
|
||||
|
@ -1,4 +1,43 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Privacy Mode"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Websites must request access to view your account information."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Expose Accounts"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "Expose accounts to the current website. Useful for legacy dapps."
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "Are you sure you want to expose your accounts to the current website?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Are you sure you want to clear approved websites?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Approved website data cleared successfully."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Privacy Data"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Clear privacy data so all websites must request access to view account information again."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Clear Privacy Data"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Reject"
|
||||
},
|
||||
"providerRequest": {
|
||||
"message": "$1 would like to connect to your account"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "This site is requesting access to view your current account address. Always make sure you trust the sites you interact with."
|
||||
},
|
||||
"accept": {
|
||||
"message": "Accept"
|
||||
},
|
||||
@ -170,6 +209,9 @@
|
||||
"connect": {
|
||||
"message": "Connect"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Connect Request"
|
||||
},
|
||||
"connecting": {
|
||||
"message": "Connecting..."
|
||||
},
|
||||
@ -692,9 +734,27 @@
|
||||
"newRecipient": {
|
||||
"message": "New Recipient"
|
||||
},
|
||||
"newRPC": {
|
||||
"newNetwork": {
|
||||
"message": "New Network"
|
||||
},
|
||||
"rpcURL": {
|
||||
"message": "New RPC URL"
|
||||
},
|
||||
"showAdvancedOptions": {
|
||||
"message": "Show Advanced Options"
|
||||
},
|
||||
"hideAdvancedOptions": {
|
||||
"message": "Hide Advanced Options"
|
||||
},
|
||||
"optionalChainId": {
|
||||
"message": "ChainID (optional)"
|
||||
},
|
||||
"optionalSymbol": {
|
||||
"message": "Symbol (optional)"
|
||||
},
|
||||
"optionalNickname": {
|
||||
"message": "Nickname (optional)"
|
||||
},
|
||||
"next": {
|
||||
"message": "Next"
|
||||
},
|
||||
@ -803,7 +863,7 @@
|
||||
"message": "Primary Currency"
|
||||
},
|
||||
"primaryCurrencySettingDescription": {
|
||||
"message": "Select ETH to prioritize displaying values in ETH. Select Fiat to prioritize displaying values in your selected currency."
|
||||
"message": "Select native to prioritize displaying values in the native currency of the chain (e.g. ETH). Select Fiat to prioritize displaying values in your selected fiat currency."
|
||||
},
|
||||
"privacyMsg": {
|
||||
"message": "Privacy Policy"
|
||||
@ -842,9 +902,6 @@
|
||||
"refundAddress": {
|
||||
"message": "Your Refund Address"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Reject"
|
||||
},
|
||||
"rejectAll": {
|
||||
"message": "Reject All"
|
||||
},
|
||||
@ -1169,12 +1226,18 @@
|
||||
"transactionUpdatedGas": {
|
||||
"message": "Transaction updated with a gas price of $1 on $2."
|
||||
},
|
||||
"transactionErrored": {
|
||||
"message": "Transaction encountered an error."
|
||||
},
|
||||
"transactions": {
|
||||
"message": "transactions"
|
||||
},
|
||||
"transactionError": {
|
||||
"message": "Transaction Error. Exception thrown in contract code."
|
||||
},
|
||||
"transactionErrorNoContract": {
|
||||
"message": "Trying to call a function on a non-contract address."
|
||||
},
|
||||
"transactionMemo": {
|
||||
"message": "Transaction memo (optional)"
|
||||
},
|
||||
|
@ -1,4 +1,49 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Modo privado"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Los sitios web deben solicitar acceso para ver la información de su cuenta."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Exponer cuentas"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "Exponer cuentas al sitio web actual. Útil para dapps heredados."
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "¿Seguro que quieres exponer tus cuentas al sitio web actual?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "¿Seguro que quieres borrar los sitios web aprobados?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Los datos aprobados del sitio web se borraron con éxito."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Datos de aprobación"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Borre los datos del sitio web aprobado para que todos los sitios deban volver a solicitar la aprobación."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Borrar datos de aprobación"
|
||||
},
|
||||
"approve": {
|
||||
"message": "Aprobar"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Rechazar"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "Solicitud de API Web3"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "Por favor, revise esta solicitud API Ethereum."
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "El dominio que se muestra a continuación intenta solicitar acceso a la API Ethereum para que pueda interactuar con la cadena de bloques de Ethereum. Siempre verifique que esté en el sitio correcto antes de aprobar el acceso Ethereum."
|
||||
},
|
||||
"accept": {
|
||||
"message": "Aceptar"
|
||||
},
|
||||
|
@ -1,4 +1,43 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Les sites Web doivent demander un accès pour afficher les informations de votre compte."
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Les sites Web doivent demander un accès pour afficher les informations de votre compte."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Exposer les comptes"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "Exposer des comptes sur le site Web actuel. Utile pour les dapps hérités."
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "Êtes-vous sûr de vouloir exposer vos comptes au site Web actuel?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Êtes-vous sûr de vouloir supprimer les sites Web approuvés?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Les données de site Web approuvées ont été supprimées."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Données d'approbation"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Effacer les données de site Web approuvées afin que tous les sites doivent à nouveau demander l'approbation."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Effacer les données d'approbation"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "Demande d'API Web3"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "Veuillez consulter cette demande d'API Ethereum."
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Le domaine répertorié ci-dessous tente de demander l'accès à l'API Ethereum pour pouvoir interagir avec la chaîne de blocs Ethereum. Vérifiez toujours que vous êtes sur le bon site avant d'autoriser l'accès à Ethereum."
|
||||
},
|
||||
"accept": {
|
||||
"message": "Accepter"
|
||||
},
|
||||
|
@ -1,4 +1,49 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "गोपनीयता मोड"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "वेबसाइटों को आपकी खाता जानकारी देखने के लिए पहुंच का अनुरोध करना होगा।"
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "खातों का पर्दाफाश करें"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "मौजूदा वेबसाइट पर खातों का पर्दाफाश करें। विरासत डैप्स के लिए उपयोगी।"
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "क्या आप वाकई अपने खातों को वर्तमान वेबसाइट पर बेनकाब करना चाहते हैं?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "क्या आप वाकई अनुमोदित वेबसाइटों को साफ़ करना चाहते हैं?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "स्वीकृत वेबसाइट डेटा सफलतापूर्वक मंजूरी दे दी।"
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "स्वीकृति डेटा"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "अनुमोदित वेबसाइट डेटा साफ़ करें ताकि सभी साइटों को फिर से अनुमोदन का अनुरोध करना होगा।"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "अनुमोदन डेटा साफ़ करें"
|
||||
},
|
||||
"approve": {
|
||||
"message": "मंजूर"
|
||||
},
|
||||
"reject": {
|
||||
"message": "अस्वीकार"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "वेब 3 एपीआई अनुरोध"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "कृपया इस वेब 3 एपीआई अनुरोध की समीक्षा करें।"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "नीचे सूचीबद्ध डोमेन वेब 3 एपीआई तक पहुंच का अनुरोध करने का प्रयास कर रहा है ताकि यह एथेरियम ब्लॉकचेन से बातचीत कर सके। वेब 3 एक्सेस को मंजूरी देने से पहले हमेशा सही जांच करें कि आप सही साइट पर हैं।"
|
||||
},
|
||||
"accept": {
|
||||
"message": "स्वीकार करें"
|
||||
},
|
||||
|
@ -1,4 +1,43 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Mòd Privacy"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Sou sit entènèt yo dwe mande aksè pou wè enfòmasyon kont ou."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Ekspoze Kont"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "Ekspoze kont sou sitwèb aktyèl la. Itil pou dapps eritaj."
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "Èske ou sèten ou vle ekspoze kont ou sou sit entènèt aktyèl la?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Èske ou sèten ou vle klè sitwèb apwouve?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Done sou sit wèb apwouve yo te klarifye avèk siksè."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Done sou vi prive"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Done sou vi prive klè pou tout sit entènèt yo dwe mande aksè pou wè enfòmasyon kont ankò."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Klè Done sou vi prive"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "Ethereum API Mande"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "Tanpri revize sa API demann Ethereum."
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Domèn ki nan lis anba a ap mande pou jwenn aksè a blòkchou Ethereum ak pou wè kont ou ye kounye a. Toujou double tcheke ke ou sou sit ki kòrèk la anvan apwouve aksè."
|
||||
},
|
||||
"accept": {
|
||||
"message": "Aksepte"
|
||||
},
|
||||
@ -14,6 +53,9 @@
|
||||
"accountName": {
|
||||
"message": "Non Kont"
|
||||
},
|
||||
"accountOptions": {
|
||||
"message": "Opsyon kont"
|
||||
},
|
||||
"accountSelectionRequired": {
|
||||
"message": "Ou bezwen chwazi yon kont!"
|
||||
},
|
||||
@ -45,7 +87,7 @@
|
||||
"message": "Kantite lajan + Gaz"
|
||||
},
|
||||
"appDescription": {
|
||||
"message": "Ethereum Ekstansyon Navigatè",
|
||||
"message": "Ekstansyon Navigatè Ethereum",
|
||||
"description": "The description of the application"
|
||||
},
|
||||
"appName": {
|
||||
@ -61,6 +103,12 @@
|
||||
"attemptingConnect": {
|
||||
"message": "Eseye konekte nan blockchain."
|
||||
},
|
||||
"attemptToCancel": {
|
||||
"message": "Eseye anile?"
|
||||
},
|
||||
"attemptToCancelDescription": {
|
||||
"message": "Soumèt tantativ sa a pa garanti ke yo pral anile tranzaksyon ou anile. Si tantativ anile an gen siksè, ou pral chaje frè yo tranzaksyon pi wo a."
|
||||
},
|
||||
"attributions": {
|
||||
"message": "Atribisyon"
|
||||
},
|
||||
@ -116,12 +164,24 @@
|
||||
"cancel": {
|
||||
"message": "Anile"
|
||||
},
|
||||
"cancelAttempt": {
|
||||
"message": "Teste Anile"
|
||||
},
|
||||
"cancellationGasFee": {
|
||||
"message": "Anilasyon Gaz Chaj"
|
||||
},
|
||||
"cancelN": {
|
||||
"message": "Anile tout $ 1 tranzaksyon"
|
||||
},
|
||||
"classicInterface": {
|
||||
"message": "Sèvi ak fas klasik la"
|
||||
},
|
||||
"clickCopy": {
|
||||
"message": "Klike sou kopi"
|
||||
},
|
||||
"clickToAdd": {
|
||||
"message": "Klike sou $ 1 pou ajoute yo nan kont ou"
|
||||
},
|
||||
"close": {
|
||||
"message": "Fèmen"
|
||||
},
|
||||
@ -144,7 +204,7 @@
|
||||
"message": "Konfime Tranzaksyon"
|
||||
},
|
||||
"connectHardwareWallet": {
|
||||
"message": "Konekte Hardware Wallet"
|
||||
"message": "Konekte Materyèl Wallet"
|
||||
},
|
||||
"connect": {
|
||||
"message": "Konekte"
|
||||
@ -152,6 +212,21 @@
|
||||
"connecting": {
|
||||
"message": "Koneksyon..."
|
||||
},
|
||||
"connectingToKovan": {
|
||||
"message": "Konekte nan Kovan Tès Rezo a"
|
||||
},
|
||||
"connectingToMainnet": {
|
||||
"message": "Konekte ak Prensipal Ethereum Rezo a"
|
||||
},
|
||||
"connectingToRopsten": {
|
||||
"message": "Konekte ak Ropsten Tès Rezo a"
|
||||
},
|
||||
"connectingToRinkeby": {
|
||||
"message": "Konekte nan Rinkeby Tès Rezo a"
|
||||
},
|
||||
"connectingToUnknown": {
|
||||
"message": "Konekte nan rezo enkoni"
|
||||
},
|
||||
"connectToLedger": {
|
||||
"message": "Konekte ak Ledger"
|
||||
},
|
||||
@ -208,14 +283,20 @@
|
||||
},
|
||||
"crypto": {
|
||||
"message": "Crypto",
|
||||
"description": "Change tip (cryptocurrencies)"
|
||||
"description": "Exchange type (cryptocurrencies)"
|
||||
},
|
||||
"currentConversion": {
|
||||
"message": "Konvèsyon aktyèl"
|
||||
},
|
||||
"currentLanguage": {
|
||||
"message": "Lang Aktyèl"
|
||||
},
|
||||
"currentNetwork": {
|
||||
"message": "Rezo aktyèl"
|
||||
},
|
||||
"currentRpc": {
|
||||
"message": "Aktyèl RPC"
|
||||
},
|
||||
"customGas": {
|
||||
"message": "Koutim Gaz"
|
||||
},
|
||||
@ -322,14 +403,8 @@
|
||||
"enterPasswordContinue": {
|
||||
"message": "Mete modpas pou kontinye"
|
||||
},
|
||||
"parameters": {
|
||||
"message": "Paramèt"
|
||||
},
|
||||
"passwordNotLongEnough": {
|
||||
"message": "Modpas la pa ase"
|
||||
},
|
||||
"passwordsDontMatch": {
|
||||
"message": "Modpas Pa Koresponn ak"
|
||||
"eth": {
|
||||
"message": "ETH"
|
||||
},
|
||||
"etherscanView": {
|
||||
"message": "Gade kont sou Etherscan"
|
||||
@ -337,6 +412,9 @@
|
||||
"exchangeRate": {
|
||||
"message": "Chanje to"
|
||||
},
|
||||
"expandView": {
|
||||
"message": "Elaji Wè"
|
||||
},
|
||||
"exportPrivateKey": {
|
||||
"message": "Voye Kòd Prive"
|
||||
},
|
||||
@ -391,8 +469,11 @@
|
||||
"gasLimitTooLow": {
|
||||
"message": "Limit gaz dwe omwen 21000"
|
||||
},
|
||||
"gasUsed": {
|
||||
"message": "Gaz yo Itilize"
|
||||
},
|
||||
"generatingSeed": {
|
||||
"message": "Génération Seed..."
|
||||
"message": "Grenn jenerasyon..."
|
||||
},
|
||||
"gasPrice": {
|
||||
"message": "Pri gaz (GWEI)"
|
||||
@ -421,16 +502,16 @@
|
||||
"description": "helper for inputting hex as decimal input"
|
||||
},
|
||||
"hardware": {
|
||||
"message": "hardware"
|
||||
"message": "materyèl"
|
||||
},
|
||||
"hardwareWalletConnected": {
|
||||
"message": "Hardware Wallet konekte"
|
||||
"message": "Materyèl Wallet konekte"
|
||||
},
|
||||
"hardwareWallets": {
|
||||
"message": "Hardware Wallet konekte"
|
||||
"message": "Materyèl Wallet konekte"
|
||||
},
|
||||
"hardwareWalletsMsg": {
|
||||
"message": "Chwazi yon Hardware Wallet ou ta renmen itilize ak MetaMask"
|
||||
"message": "Chwazi yon Materyèl Wallet ou ta renmen itilize ak MetaMask"
|
||||
},
|
||||
"havingTroubleConnecting": {
|
||||
"message": "Èske w gen pwoblèm pou konekte?"
|
||||
@ -486,6 +567,9 @@
|
||||
"importUsingSeed": {
|
||||
"message": "Pòte lè sèvi avèk seed fraz"
|
||||
},
|
||||
"info": {
|
||||
"message": "Enfo"
|
||||
},
|
||||
"infoHelp": {
|
||||
"message": "Enfo & Èd"
|
||||
},
|
||||
@ -579,7 +663,7 @@
|
||||
"message": "seed mo sèlman gen karaktè miniskil"
|
||||
},
|
||||
"mainnet": {
|
||||
"message": "Main Ethereum Network"
|
||||
"message": "Prensipal Ethereum Rezo a"
|
||||
},
|
||||
"menu": {
|
||||
"message": "Opsyon"
|
||||
@ -593,9 +677,15 @@
|
||||
"metamaskSeedWords": {
|
||||
"message": "MetaMask Seed Mo"
|
||||
},
|
||||
"metamaskVersion": {
|
||||
"message": "MetaMask Vèsyon"
|
||||
},
|
||||
"min": {
|
||||
"message": "Minimòm"
|
||||
},
|
||||
"missingYourTokens": {
|
||||
"message": "Ou pa wè token ou a?"
|
||||
},
|
||||
"myAccounts": {
|
||||
"message": "Kont mwen"
|
||||
},
|
||||
@ -635,6 +725,9 @@
|
||||
"newPassword": {
|
||||
"message": "Nouvo modpas (minit 8)"
|
||||
},
|
||||
"newPassword8Chars": {
|
||||
"message": "Nouvo modpas (minit 8)"
|
||||
},
|
||||
"newRecipient": {
|
||||
"message": "Nouvo Benefisyè"
|
||||
},
|
||||
@ -677,6 +770,13 @@
|
||||
"oldUIMessage": {
|
||||
"message": "Ou te retounen nan Ansyen Itilizatè kouòdone. Ou ka chanje tounen nan nouvo Ansyen Itilizatè nan opsyon a nan meni an tèt la."
|
||||
},
|
||||
"onlySendToEtherAddress": {
|
||||
"message": "Sèlman voye ETH nan yon adrès Ethereum."
|
||||
},
|
||||
"onlySendTokensToAccountAddress": {
|
||||
"message": "Sèlman voye $ 1 nan yon adrès kont Ethereum.",
|
||||
"description": "displays token symbol"
|
||||
},
|
||||
"openInTab": {
|
||||
"message": "Louvri nan etikèt"
|
||||
},
|
||||
@ -684,19 +784,34 @@
|
||||
"message": "oubyen",
|
||||
"description": "choice between creating or importing a new account"
|
||||
},
|
||||
"orderOneHere": {
|
||||
"message": "Mete nan lòd on Trezor oswa Ledger epi kenbe lajan ou nan yon stòk frèt."
|
||||
},
|
||||
"origin": {
|
||||
"message": "Orijin"
|
||||
},
|
||||
"outgoing": {
|
||||
"message": "Ap kite"
|
||||
},
|
||||
"parameters": {
|
||||
"message": "Paramèt"
|
||||
},
|
||||
"password": {
|
||||
"message": "Modpas"
|
||||
},
|
||||
"passwordCorrect": {
|
||||
"message": "Tanpri asire ke modpas ou kòrèk."
|
||||
},
|
||||
"passwordsDontMatch": {
|
||||
"message": "Modpas pa matche"
|
||||
},
|
||||
"passwordMismatch": {
|
||||
"message": "modpas sa pa menm",
|
||||
"description": "in password creation process, the two new password fields did not match"
|
||||
},
|
||||
"passwordNotLongEnough": {
|
||||
"message": "Modpas pa lontan ase"
|
||||
},
|
||||
"passwordShort": {
|
||||
"message": "modpas pa sifi",
|
||||
"description": "in password creation process, the password is not long enough to be secure"
|
||||
@ -723,6 +838,12 @@
|
||||
"prev": {
|
||||
"message": "Avan"
|
||||
},
|
||||
"primaryCurrencySetting": {
|
||||
"message": "Lajan ou itilize pi plis la"
|
||||
},
|
||||
"primaryCurrencySettingDescription": {
|
||||
"message": "Chwazi ETH pou bay priyorite montre valè nan ETH. Chwazi Fiat priyorite montre valè nan lajan ou chwazi a."
|
||||
},
|
||||
"privacyMsg": {
|
||||
"message": "Règleman sou enfòmasyon prive"
|
||||
},
|
||||
@ -760,9 +881,21 @@
|
||||
"refundAddress": {
|
||||
"message": "Adrès pou resevwa"
|
||||
},
|
||||
"rejected": {
|
||||
"reject": {
|
||||
"message": "Rejte"
|
||||
},
|
||||
"rejectAll": {
|
||||
"message": "Rejte Tout"
|
||||
},
|
||||
"rejectTxsN": {
|
||||
"message": "Rejete $ 1 tranzaksyon"
|
||||
},
|
||||
"rejectTxsDescription": {
|
||||
"message": "Ou se sou rejte $ 1 yon anpil nan tranzaksyon yo."
|
||||
},
|
||||
"rejected": {
|
||||
"message": "Rejete"
|
||||
},
|
||||
"reset": {
|
||||
"message": "Repwograme"
|
||||
},
|
||||
@ -787,9 +920,6 @@
|
||||
"retryWithMoreGas": {
|
||||
"message": "Reseye ak yon pri gaz pi wo isit la"
|
||||
},
|
||||
"walletSeed": {
|
||||
"message": "Wallet Seed"
|
||||
},
|
||||
"restore": {
|
||||
"message": "Retabli"
|
||||
},
|
||||
@ -832,24 +962,6 @@
|
||||
"rpc": {
|
||||
"message": "Koutim RPC"
|
||||
},
|
||||
"currentRpc": {
|
||||
"message": "Kounya RPC"
|
||||
},
|
||||
"connectingToMainnet": {
|
||||
"message": "Konekte ak Main (Prensipal) Ethereum Rezo a"
|
||||
},
|
||||
"connectingToRopsten": {
|
||||
"message": "Konekte ak Ropsten Tès Rezo a"
|
||||
},
|
||||
"connectingToKovan": {
|
||||
"message": "Konekte nan Kovan Tès Rezo a"
|
||||
},
|
||||
"connectingToRinkeby": {
|
||||
"message": "Konekte nan Rinkeby Tès Rezo a"
|
||||
},
|
||||
"connectingToUnknown": {
|
||||
"message": "Konekte nan rezo enkoni"
|
||||
},
|
||||
"sampleAccountName": {
|
||||
"message": "Pa egzanp, Nouvo kont mwen an",
|
||||
"description": "Help user understand concept of adding a human-readable name to their account"
|
||||
@ -857,15 +969,6 @@
|
||||
"save": {
|
||||
"message": "Sove"
|
||||
},
|
||||
"speedUp": {
|
||||
"message": "pi vit"
|
||||
},
|
||||
"speedUpTitle": {
|
||||
"message": "Monte vitès tranzaksyon"
|
||||
},
|
||||
"speedUpSubtitle": {
|
||||
"message": "Ogmante pri gaz ou pou eseye efase tranzaksyon ou pi vit"
|
||||
},
|
||||
"saveAsCsvFile": {
|
||||
"message": "Sove kòm dosye CSV"
|
||||
},
|
||||
@ -876,6 +979,12 @@
|
||||
"saveSeedAsFile": {
|
||||
"message": "Sove pawòl seed kòm dosye"
|
||||
},
|
||||
"scanInstructions": {
|
||||
"message": "Mete kòd QR la devan kamera ou"
|
||||
},
|
||||
"scanQrCode": {
|
||||
"message": "Enspeksyon QR Kòd"
|
||||
},
|
||||
"search": {
|
||||
"message": "Rechèch"
|
||||
},
|
||||
@ -885,15 +994,6 @@
|
||||
"secretPhrase": {
|
||||
"message": "Antre fraz sekrè douz mo ou a pou w restore kòf ou a."
|
||||
},
|
||||
"showHexData": {
|
||||
"message": "Montre Hex Data"
|
||||
},
|
||||
"showHexDataDescription": {
|
||||
"message": "Pran sa pouw ka montre chan entèfas hex data a"
|
||||
},
|
||||
"newPassword8Chars": {
|
||||
"message": "Nouvo modpas (pou pi pit 8)"
|
||||
},
|
||||
"seedPhraseReq": {
|
||||
"message": "Seed fraz yo se 12 long mo"
|
||||
},
|
||||
@ -903,6 +1003,9 @@
|
||||
"selectCurrency": {
|
||||
"message": "Chwazi Lajan"
|
||||
},
|
||||
"selectLocale": {
|
||||
"message": "Chwazi Lokasyon"
|
||||
},
|
||||
"selectService": {
|
||||
"message": "Chwazi Sèvis"
|
||||
},
|
||||
@ -927,19 +1030,6 @@
|
||||
"separateEachWord": {
|
||||
"message": "Separe chak mo ak yon sèl espas"
|
||||
},
|
||||
"onlySendToEtherAddress": {
|
||||
"message": "Sèlman voye ETH nan yon adrès Ethereum."
|
||||
},
|
||||
"onlySendTokensToAccountAddress": {
|
||||
"message": "Sèlman voye $ 1 nan yon adrès kont Ethereum.",
|
||||
"description": "displays token symbol"
|
||||
},
|
||||
"orderOneHere": {
|
||||
"message": "Mete nan lòd on Trezor oswa Ledger epi kenbe lajan ou nan yon stòk frèt."
|
||||
},
|
||||
"outgoing": {
|
||||
"message": "Ap kite"
|
||||
},
|
||||
"searchTokens": {
|
||||
"message": "Rechèch Tokens"
|
||||
},
|
||||
@ -964,33 +1054,6 @@
|
||||
"settings": {
|
||||
"message": "Paramèt"
|
||||
},
|
||||
"step1HardwareWallet": {
|
||||
"message": "1. Konekte Materyèl bous"
|
||||
},
|
||||
"step1HardwareWalletMsg": {
|
||||
"message": "Konekte materyèl bous ou dirèkteman nan òdinatè ou."
|
||||
},
|
||||
"step2HardwareWallet": {
|
||||
"message": "2. Chwazi yon kont"
|
||||
},
|
||||
"step2HardwareWalletMsg": {
|
||||
"message": "Chwazi kont ou vle wè a. Ou ka chwazi youn sèlman nan yon moman."
|
||||
},
|
||||
"step3HardwareWallet": {
|
||||
"message": "3. Kòmanse itilize dApps ak plis ankò!"
|
||||
},
|
||||
"step3HardwareWalletMsg": {
|
||||
"message": "Sèvi ak kont materyèl ou menm jan ou t ap fè pou kont Etherum. Ouvri sesyon an nan dApps, voye Eth, achte ak stòke ERC20 tokens ak e ki pake chanje tokens tankou CryptoKitties."
|
||||
},
|
||||
"info": {
|
||||
"message": "Enfòmasyon"
|
||||
},
|
||||
"scanInstructions": {
|
||||
"message": "Mete kòd QR la devan kamera ou"
|
||||
},
|
||||
"scanQrCode": {
|
||||
"message": "Enspeksyon QR Kòd"
|
||||
},
|
||||
"shapeshiftBuy": {
|
||||
"message": "Achte avèk Shapeshift"
|
||||
},
|
||||
@ -1000,6 +1063,12 @@
|
||||
"showQRCode": {
|
||||
"message": "Montre Kòd QR"
|
||||
},
|
||||
"showHexData": {
|
||||
"message": "Montre Hex Data"
|
||||
},
|
||||
"showHexDataDescription": {
|
||||
"message": "Pran sa pouw ka montre chan entèfas hex data a"
|
||||
},
|
||||
"sign": {
|
||||
"message": "Siyen"
|
||||
},
|
||||
@ -1024,6 +1093,15 @@
|
||||
"spaceBetween": {
|
||||
"message": "ka gen sèlman yon espas ant mo yo"
|
||||
},
|
||||
"speedUp": {
|
||||
"message": "pi vit"
|
||||
},
|
||||
"speedUpTitle": {
|
||||
"message": "Monte vitès tranzaksyon"
|
||||
},
|
||||
"speedUpSubtitle": {
|
||||
"message": "Ogmante pri gaz ou pou eseye efase tranzaksyon ou pi vit"
|
||||
},
|
||||
"status": {
|
||||
"message": "Kondisyon"
|
||||
},
|
||||
@ -1036,6 +1114,24 @@
|
||||
"stateLogError": {
|
||||
"message": "Erè nan retwouve State Logs yo."
|
||||
},
|
||||
"step1HardwareWallet": {
|
||||
"message": "1. Konekte Materyèl bous"
|
||||
},
|
||||
"step1HardwareWalletMsg": {
|
||||
"message": "Konekte materyèl bous ou dirèkteman nan òdinatè ou."
|
||||
},
|
||||
"step2HardwareWallet": {
|
||||
"message": "2. Chwazi yon kont"
|
||||
},
|
||||
"step2HardwareWalletMsg": {
|
||||
"message": "Chwazi kont ou vle wè a. Ou ka chwazi youn sèlman nan yon moman."
|
||||
},
|
||||
"step3HardwareWallet": {
|
||||
"message": "3. Kòmanse itilize dApps ak plis ankò!"
|
||||
},
|
||||
"step3HardwareWalletMsg": {
|
||||
"message": "Sèvi ak kont materyèl ou menm jan ou t ap fè pou kont Etherum. Ouvri sesyon an nan dApps, voye Eth, achte ak stòke ERC20 tokens ak e ki pake chanje tokens tankou CryptoKitties."
|
||||
},
|
||||
"submit": {
|
||||
"message": "Soumèt"
|
||||
},
|
||||
@ -1088,7 +1184,7 @@
|
||||
"total": {
|
||||
"message": "Total"
|
||||
},
|
||||
"transactions": {
|
||||
"transaction": {
|
||||
"message": "tranzaksyon yo"
|
||||
},
|
||||
"transactionConfirmed": {
|
||||
@ -1097,6 +1193,9 @@
|
||||
"transactionCreated": {
|
||||
"message": "Tranzaksyon ou te kreye avèk on valè de $1 pou $2."
|
||||
},
|
||||
"transactionWithNonce": {
|
||||
"message": "Tranzaksyon $1"
|
||||
},
|
||||
"transactionDropped": {
|
||||
"message": "Tranzaksyon ou te tonbe a $2."
|
||||
},
|
||||
@ -1109,6 +1208,9 @@
|
||||
"transactionUpdatedGas": {
|
||||
"message": "Tranzaksyon ou te aktyalize avèk on pri gaz de $1 a $2."
|
||||
},
|
||||
"transactions": {
|
||||
"message": "transactions"
|
||||
},
|
||||
"transactionError": {
|
||||
"message": "Erè tranzaksyon. Eksepsyon jete nan kòd kontra."
|
||||
},
|
||||
@ -1121,6 +1223,9 @@
|
||||
"transfer": {
|
||||
"message": "Transfè"
|
||||
},
|
||||
"transferFrom": {
|
||||
"message": "Transfer From"
|
||||
},
|
||||
"transfers": {
|
||||
"message": "Transfè yo"
|
||||
},
|
||||
@ -1182,6 +1287,9 @@
|
||||
"unlockMessage": {
|
||||
"message": "Entènèt desantralize a ap tann"
|
||||
},
|
||||
"updatedWithDate": {
|
||||
"message": "Mete ajou $1"
|
||||
},
|
||||
"uriErrorMsg": {
|
||||
"message": "URIs mande pou apwopriye prefiks HTTP / HTTPS a."
|
||||
},
|
||||
@ -1210,11 +1318,14 @@
|
||||
"visitWebSite": {
|
||||
"message": "Vizite sit entènèt nou an"
|
||||
},
|
||||
"walletSeed": {
|
||||
"message": "Bous Seed"
|
||||
},
|
||||
"warning": {
|
||||
"message": "Avètisman"
|
||||
},
|
||||
"welcomeBack": {
|
||||
"message": "Bon retou!"
|
||||
"message": "Bon Retou!"
|
||||
},
|
||||
"welcomeBeta": {
|
||||
"message": "Byenveni nan MetaMask Beta"
|
||||
@ -1222,6 +1333,9 @@
|
||||
"whatsThis": {
|
||||
"message": "Kisa sa ye?"
|
||||
},
|
||||
"yesLetsTry": {
|
||||
"message": "Wi, ann eseye"
|
||||
},
|
||||
"youNeedToAllowCameraAccess": {
|
||||
"message": "Ou bezwen bay kamera aksè pou sèvi ak fonksyon sa."
|
||||
},
|
||||
|
@ -1,4 +1,46 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Modalità di privacy"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "I siti Web devono richiedere l'accesso per visualizzare le informazioni del tuo account."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Expose Accounts"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "Esporre gli account al sito Web corrente. Utile per dapps legacy."
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "Sei sicuro di voler esporre i tuoi account al sito web corrente?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Sei sicuro di voler cancellare i siti Web approvati?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Dati del sito Web approvati cancellati correttamente."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Dati di approvazione"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Cancella i dati del sito web approvati, quindi tutti i siti devono richiedere nuovamente l'approvazione."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Cancella i dati di approvazione"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Rifiutare"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "Richiesta API Web3"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "Si prega di rivedere questa richiesta API Ethereum."
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Il dominio elencato di seguito sta tentando di richiedere l'accesso all'API Ethereum in modo che possa interagire con la blockchain di Ethereum. Controlla sempre di essere sul sito corretto prima di approvare l'accesso a Ethereum."
|
||||
},
|
||||
"accept": {
|
||||
"message": "Accetta"
|
||||
},
|
||||
@ -824,9 +866,6 @@
|
||||
"refundAddress": {
|
||||
"message": "Indirizzo di Rimborso"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Reject"
|
||||
},
|
||||
"rejectAll": {
|
||||
"message": "Reject All"
|
||||
},
|
||||
|
@ -1,4 +1,49 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "プライバシーモード"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "ウェブサイトはあなたのアカウント情報を閲覧するためのアクセスを要求する必要があります。"
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "アカウントを公開する"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "アカウントを現在のウェブサイトに公開する。従来のdappsに役立ちます。"
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "現在のウェブサイトにアカウントを公開してもよろしいですか?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "承認されたウェブサイトをクリアしてもよろしいですか?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "承認されたウェブサイトデータが正常に消去されました。"
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "承認データ"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "承認されたウェブサイトのデータをクリアすると、すべてのサイトで承認を再度要求する必要があります"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "承認データのクリア"
|
||||
},
|
||||
"approve": {
|
||||
"message": "承認する"
|
||||
},
|
||||
"reject": {
|
||||
"message": "拒否"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "Web3 APIリクエスト"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "このEthereum APIリクエストを確認してください。"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "下記のドメインは、Ethereumブロックチェーンとやり取りできるようにEthereum APIへのアクセスをリクエストしようとしています。 Web3アクセスを承認する前に、正しいサイトにいることを常に確認してください。"
|
||||
},
|
||||
"accept": {
|
||||
"message": "承認"
|
||||
},
|
||||
|
@ -1,4 +1,46 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "개인 정보 보호 모드"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "웹 사이트는 계정 정보를 볼 수있는 액세스 권한을 요청해야합니다."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "계정 노출"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "계정을 현재 웹 사이트에 노출하십시오. 기존 dapps에 유용합니다."
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "계정을 현재 웹 사이트에 공개 하시겠습니까?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "승인 된 웹 사이트를 삭제 하시겠습니까?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "승인 된 웹 사이트 데이터가 성공적으로 삭제되었습니다."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "승인 데이터"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "승인 된 웹 사이트 데이터를 삭제하여 모든 사이트에서 승인을 다시 요청해야합니다."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "승인 데이터 삭제"
|
||||
},
|
||||
"reject": {
|
||||
"message": "받지 않다"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "Web3 API 요청"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "이 Ethereum API 요청을 검토하십시오."
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "아래 나열된 도메인은 Web3 API에 대한 액세스를 요청하여 Ethereum 블록 체인과 상호 작용할 수 있습니다. Ethereum 액세스를 승인하기 전에 항상 올바른 사이트에 있는지 다시 확인하십시오."
|
||||
},
|
||||
"accept": {
|
||||
"message": "수락"
|
||||
},
|
||||
@ -815,9 +857,6 @@
|
||||
"refundAddress": {
|
||||
"message": "환불받을 주소"
|
||||
},
|
||||
"reject": {
|
||||
"message": "거부"
|
||||
},
|
||||
"rejectAll": {
|
||||
"message": "모두 거부"
|
||||
},
|
||||
|
@ -1,4 +1,49 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Privacy-modus"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Websites moeten toegang vragen om uw accountgegevens te bekijken."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Expose Accounts"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "Stel accounts vrij op de huidige website. Handig voor legacy dapps."
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "Weet u zeker dat u uw accounts wilt weergeven aan de huidige website?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Weet je zeker dat je goedgekeurde websites wilt wissen?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Goedgekeurde websitegegevens zijn met succes gewist."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Goedkeuringsgegevens"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Goedgekeurde websitegegevens wissen zodat alle sites opnieuw goedkeuring moeten aanvragen."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Gegevens over goedkeuring wissen"
|
||||
},
|
||||
"approve": {
|
||||
"message": "Goedkeuren"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Afwijzen"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "Web3 API-aanvraag"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "Bekijk deze Ethereum API-aanvraag."
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Het onderstaande domein probeert toegang tot de Ethereum API te vragen zodat deze kan communiceren met de Ethereum-blockchain. Controleer altijd eerst of u op de juiste site bent voordat u Ethereum-toegang goedkeurt."
|
||||
},
|
||||
"accept": {
|
||||
"message": "Aanvaarden"
|
||||
},
|
||||
@ -435,7 +480,7 @@
|
||||
"message": "back-up woorden hebben alleen kleine letters"
|
||||
},
|
||||
"mainnet": {
|
||||
"message": "belangrijkste Ethereum-netwerk"
|
||||
"message": "Main Netwerk"
|
||||
},
|
||||
"message": {
|
||||
"message": "Bericht"
|
||||
|
@ -1,4 +1,49 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Mode ng Privacy"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Dapat humiling ng access ang mga website upang tingnan ang impormasyon ng iyong account."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Ilantad ang Mga Account"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "Ilantad ang mga account sa kasalukuyang website. Kapaki-pakinabang para sa mga dapps ng legacy."
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "Sigurado ka bang gusto mong ilantad ang iyong mga account sa kasalukuyang website?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Sigurado ka bang gusto mong i-clear ang mga naaprubahang website?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Matagumpay na na-clear ang data ng aprubadong website."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Data ng Pag-apruba"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "I-clear ang naaprubahang data ng website upang ang lahat ng site ay dapat humiling muli ng pag-apruba"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Tanggalin ang data ng pag-apruba"
|
||||
},
|
||||
"approve": {
|
||||
"message": "Aprubahan"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Tanggihan"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "Kahilingan sa Web3 API"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "Mangyaring suriin ang kahilingan sa Ethereum API na ito."
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Ang domain na nakalista sa ibaba ay sinusubukang humiling ng access sa Ethereum API upang maaari itong makipag-ugnayan sa Ethereum blockchain. Laging i-double check na ikaw ay nasa tamang site bago aprubahan ang Ethereum access."
|
||||
},
|
||||
"accept": {
|
||||
"message": "Tanggapin"
|
||||
},
|
||||
|
@ -1,4 +1,49 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Modo de privacidade"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Os sites devem solicitar acesso para visualizar as informações da sua conta."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Expor contas"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "Exponha contas ao site atual. Útil para dapps herdados."
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "Tem certeza de que deseja expor suas contas ao site atual?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Tem certeza de que deseja limpar sites aprovados?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Dados aprovados do website foram limpos com sucesso."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Dados de aprovação"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Limpe os dados aprovados do website para que todos os sites solicitem aprovação novamente."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Limpar dados de aprovação"
|
||||
},
|
||||
"approve": {
|
||||
"message": "Aprovar"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Rejeitar"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "Solicitação de API do Web3"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "Por favor, revise esta solicitação da API da Ethereum."
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "O domínio listado abaixo está tentando solicitar acesso à API Ethereum para que ele possa interagir com o blockchain Ethereum. Sempre verifique se você está no site correto antes de aprovar o acesso à Ethereum."
|
||||
},
|
||||
"accept": {
|
||||
"message": "Aceitar"
|
||||
},
|
||||
|
@ -1,4 +1,49 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Режим конфиденциальности"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Веб-сайты должны запрашивать доступ для просмотра информации об учетной записи."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Открыть счета"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "Выводить счета на текущий веб-сайт. Полезно для старых dapps."
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "Вы уверены, что хотите открыть свои аккаунты на текущем веб-сайте?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Вы уверены, что хотите очистить утвержденные веб-сайты?Tem certeza de que deseja limpar sites aprovados?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Утвержденные данные веб-сайта успешно удалены."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Данные об утверждении"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Очистите утвержденные данные веб-сайта, чтобы все сайты снова запросили подтверждение."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Четкие данные об утверждении"
|
||||
},
|
||||
"approve": {
|
||||
"message": "Одобрить"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Отклонить"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "Запрос API Web3"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "Просмотрите этот запрос API Ethereum."
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Домен, указанный ниже, пытается запросить доступ к API-интерфейсу Ethereum, чтобы он мог взаимодействовать с блокчейном Ethereum. Всегда проверяйте, что вы находитесь на правильном сайте, прежде чем одобрять доступ к веб-сайту."
|
||||
},
|
||||
"accept": {
|
||||
"message": "Принять"
|
||||
},
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,49 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "โหมดความเป็นส่วนตัว"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "เว็บไซต์ต้องขอเข้าถึงเพื่อดูข้อมูลบัญชีของคุณ"
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "เปิดเผยบัญชี"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "เปิดเผยบัญชีไปยังเว็บไซต์ปัจจุบัน มีประโยชน์สำหรับ dapps แบบเดิม"
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "คุณแน่ใจหรือไม่ว่าต้องการเปิดเผยบัญชีของคุณไปยังเว็บไซต์ปัจจุบัน"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "คุณแน่ใจหรือไม่ว่าต้องการล้างเว็บไซต์ที่ผ่านการอนุมัติ"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "อนุมัติข้อมูลเว็บไซต์ที่ได้รับอนุมัติแล้ว"
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "ข้อมูลการอนุมัติ"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "ล้างข้อมูลเว็บไซต์ที่ได้รับการอนุมัติเพื่อให้ทุกไซต์ต้องขออนุมัติอีกครั้ง"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "ล้างข้อมูลการอนุมัติ"
|
||||
},
|
||||
"approve": {
|
||||
"message": "อนุมัติ"
|
||||
},
|
||||
"reject": {
|
||||
"message": "ปฏิเสธ"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "คำขอ Web3 API"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "โปรดอ่านคำขอ Ethereum API นี้"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "โดเมนที่แสดงด้านล่างกำลังพยายามขอเข้าถึง API ของ Ethereum เพื่อให้สามารถโต้ตอบกับบล็อค Ethereum ได้ ตรวจสอบว่าคุณอยู่ในไซต์ที่ถูกต้องก่อนที่จะอนุมัติการเข้าถึง Ethereum เสมอ"
|
||||
},
|
||||
"accept": {
|
||||
"message": "ยอมรับ"
|
||||
},
|
||||
|
@ -1,4 +1,49 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "தனியுரிமை முறை"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "உங்கள் கணக்குத் தகவலை பார்வையிட வலைத்தளங்கள் அணுகலைக் கோர வேண்டும்."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "கணக்குகளை அம்பலப்படுத்துங்கள்"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "தற்போதைய இணையதளத்திற்கு கணக்குகளை அம்பலப்படுத்துங்கள். மரபணு டாப்ஸ் பயன்படுத்த."
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "நிச்சயமாக உங்கள் கணக்குகளை தற்போதைய இணையத்தளத்தில் அம்பலப்படுத்த விரும்புகிறீர்களா?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "அங்கீகரிக்கப்பட்ட வலைத்தளங்களை நிச்சயமாக நீக்க விரும்புகிறீர்களா?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "அங்கீகரிக்கப்பட்ட வலைத்தள தரவு வெற்றிகரமாக அழிக்கப்பட்டது."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "ஒப்புதல் தரவு"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "அங்கீகரிக்கப்பட்ட வலைத்தள தரவை அழிக்கவும், அனைத்து தளங்களும் ஒப்புதல் மீண்டும் கோர வேண்டும்."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "ஒப்புதல் தரவை அழி"
|
||||
},
|
||||
"approve": {
|
||||
"message": "ஒப்புதல்"
|
||||
},
|
||||
"reject": {
|
||||
"message": "நிராகரி"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "Web3 API கோரிக்கை"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "இந்த வலை 3 API கோரிக்கையை மதிப்பாய்வு செய்யவும்."
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "கீழே பட்டியலிடப்பட்டுள்ள டொமைன் Web3 ஏபிஐ அணுகலைக் கோருவதற்கு முயற்சிக்கிறது, எனவே இது Ethereum blockchain உடன் தொடர்பு கொள்ள முடியும். Web3 அணுகுமுறையை அங்கீகரிப்பதற்கு முன் சரியான தளத்தில் இருப்பதை எப்போதும் இருமுறை சரிபார்க்கவும்."
|
||||
},
|
||||
"accept": {
|
||||
"message": "ஏற்கவும்"
|
||||
},
|
||||
|
@ -1,4 +1,49 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Gizlilik modu"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Web siteleri, hesap bilgilerinizi görmek için erişim istemek zorundadır."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Hesapları Açığa Çıkar"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "Hesapları mevcut web sitesine gösterin. Eski dapps için kullanışlıdır."
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "Hesaplarınızı mevcut web sitesine taşımak istediğinizden emin misiniz?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Onaylanmış web sitelerini silmek istediğinizden emin misiniz?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Onaylanan web sitesi verileri başarıyla temizlendi."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Onay Verileri"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Onaylanan web sitesi verilerini temizle, tüm sitelerin tekrar onay isteğinde bulunması gerekir."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Onay verilerini temizle"
|
||||
},
|
||||
"approve": {
|
||||
"message": "Onaylamak"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Reddetmek"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "Web3 API İsteği"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "Lütfen bu Ethereum API isteğini inceleyin."
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Aşağıda listelenen etki alanı, Ethereum API'sine erişim talep etmeye çalışmaktadır, böylece Ethereum blockchain ile etkileşime girebilir. Web3 erişimini onaylamadan önce her zaman doğru sitede olduğunuzu kontrol edin."
|
||||
},
|
||||
"accept": {
|
||||
"message": "Kabul et"
|
||||
},
|
||||
|
@ -1,4 +1,49 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Chế độ riêng tư"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Trang web phải yêu cầu quyền truy cập để xem thông tin tài khoản của bạn."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Hiển thị tài khoản"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "Hiển thị tài khoản cho trang web hiện tại. Hữu ích cho các ứng dụng cũ."
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "Bạn có chắc chắn muốn hiển thị tài khoản của mình cho trang web hiện tại không?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Bạn có chắc chắn muốn xóa các trang web được phê duyệt không?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Đã xóa thành công dữ liệu trang web được phê duyệt."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Dữ liệu phê duyệt"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Xóa dữ liệu trang web được phê duyệt để tất cả các trang web phải yêu cầu phê duyệt lại."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Xóa dữ liệu phê duyệt"
|
||||
},
|
||||
"approve": {
|
||||
"message": "Phê duyệt"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Từ chối"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "Yêu cầu API Web3"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "Vui lòng xem lại yêu cầu API Ethereum này."
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Miền được liệt kê bên dưới đang cố gắng yêu cầu quyền truy cập vào API Ethereum để nó có thể tương tác với chuỗi khối Ethereum. Luôn kiểm tra kỹ xem bạn có đang ở đúng trang web trước khi phê duyệt quyền truy cập Ethereum hay không."
|
||||
},
|
||||
"accept": {
|
||||
"message": "Chấp nhận"
|
||||
},
|
||||
|
@ -1,4 +1,49 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "隐私模式"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "网站必须请求访问权限才能查看您的帐户信息。"
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "公开账户"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "将帐户公开给当前网站。对传统dapps很有用。"
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "您确定要将帐户公开到当前网站吗?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "您确定要清除已批准的网站吗?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "已批准的网站数据已成功清除。"
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "审批数据"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "清除已批准的网站数据,以便所有网站都必须再次申请"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "清除批准数据"
|
||||
},
|
||||
"approve": {
|
||||
"message": "批准"
|
||||
},
|
||||
"reject": {
|
||||
"message": "拒绝"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "Web3 API请求"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "请查看此Ethereum API请求。"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "下面列出的域正在尝试请求访问Ethereum API,以便它可以与以太坊区块链进行交互。在批准Ethereum访问之前,请务必仔细检查您是否在正确的站点上。"
|
||||
},
|
||||
"accept": {
|
||||
"message": "接受"
|
||||
},
|
||||
|
@ -1,4 +1,49 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "隱私模式"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "網站必須請求訪問權限才能查看您的帳戶信息。"
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "公開賬戶"
|
||||
},
|
||||
"exposeDescription": {
|
||||
"message": "將帳戶公開給當前網站。對傳統dapps很有用。"
|
||||
},
|
||||
"confirmExpose": {
|
||||
"message": "您確定要將帳戶公開到當前網站嗎?"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "您確定要清除已批准的網站嗎?"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "已批准的網站數據已成功清除。"
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "審批數據"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "清除已批准的網站數據,以便所有網站都必須再次申請"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "清除批准數據"
|
||||
},
|
||||
"approve": {
|
||||
"message": "批准"
|
||||
},
|
||||
"reject": {
|
||||
"message": "拒絕"
|
||||
},
|
||||
"providerAPIRequest": {
|
||||
"message": "Web3 API請求"
|
||||
},
|
||||
"reviewProviderRequest": {
|
||||
"message": "請查看此Ethereum API請求。"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "下面列出的域正在嘗試請求訪問Ethereum API,以便它可以與以太坊區塊鏈進行交互。在批准Ethereum訪問之前,請務必仔細檢查您是否在正確的站點上。"
|
||||
},
|
||||
"accept": {
|
||||
"message": "接受"
|
||||
},
|
||||
|
7
app/images/mm-secure.svg
Normal file
7
app/images/mm-secure.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 10 KiB |
20
app/images/provider-approval-check.svg
Normal file
20
app/images/provider-approval-check.svg
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d)">
|
||||
<path d="M34.5 19C34.5 27.0081 28.0081 33.5 20 33.5C11.9919 33.5 5.5 27.0081 5.5 19C5.5 10.9919 11.9919 4.5 20 4.5C28.0081 4.5 34.5 10.9919 34.5 19Z" fill="#61BA00" stroke="#61BA00"/>
|
||||
<path d="M13.2998 19.7195L16.813 23.3195L26.1998 14.0195" stroke="white" stroke-width="2"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d" x="0" y="0" width="40" height="40" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="2.5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -1,5 +1,9 @@
|
||||
<html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>MetaMask Loading</title>
|
||||
<style>
|
||||
#div-logo {
|
||||
@ -31,5 +35,11 @@
|
||||
<div id="div-logo">
|
||||
<img id="logo" src="./images/loginglogo.svg">
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
// redirect to 404 after one minute
|
||||
setTimeout(() => {
|
||||
location.href = './404.html'
|
||||
}, 60000)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "__MSG_appName__",
|
||||
"short_name": "__MSG_appName__",
|
||||
"version": "4.16.0",
|
||||
"version": "5.0.0",
|
||||
"manifest_version": 2,
|
||||
"author": "https://metamask.io",
|
||||
"description": "__MSG_appDescription__",
|
||||
|
@ -23,13 +23,13 @@ const createStreamSink = require('./lib/createStreamSink')
|
||||
const NotificationManager = require('./lib/notification-manager.js')
|
||||
const MetamaskController = require('./metamask-controller')
|
||||
const rawFirstTimeState = require('./first-time-state')
|
||||
const setupRaven = require('./lib/setupRaven')
|
||||
const setupSentry = require('./lib/setupSentry')
|
||||
const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry')
|
||||
const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
|
||||
const EdgeEncryptor = require('./edge-encryptor')
|
||||
const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code')
|
||||
const getObjStructure = require('./lib/getObjStructure')
|
||||
const ipfsContent = require('./lib/ipfsContent.js')
|
||||
const setupEnsIpfsResolver = require('./lib/ens-ipfs/setup')
|
||||
|
||||
const {
|
||||
ENVIRONMENT_TYPE_POPUP,
|
||||
@ -50,7 +50,7 @@ global.METAMASK_NOTIFIER = notificationManager
|
||||
|
||||
// setup sentry error reporting
|
||||
const release = platform.getVersion()
|
||||
const raven = setupRaven({ release })
|
||||
const sentry = setupSentry({ release })
|
||||
|
||||
// browser check if it is Edge - https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
|
||||
// Internet Explorer 6-11
|
||||
@ -58,7 +58,6 @@ const isIE = !!document.documentMode
|
||||
// Edge 20+
|
||||
const isEdge = !isIE && !!window.StyleMedia
|
||||
|
||||
let ipfsHandle
|
||||
let popupIsOpen = false
|
||||
let notificationIsOpen = false
|
||||
const openMetamaskTabsIDs = {}
|
||||
@ -164,7 +163,6 @@ async function initialize () {
|
||||
const initLangCode = await getFirstPreferredLangCode()
|
||||
await setupController(initState, initLangCode)
|
||||
log.debug('MetaMask initialization complete.')
|
||||
ipfsHandle = ipfsContent(initState.NetworkController.provider)
|
||||
}
|
||||
|
||||
//
|
||||
@ -197,14 +195,14 @@ async function loadStateFromPersistence () {
|
||||
// we were able to recover (though it might be old)
|
||||
versionedData = diskStoreState
|
||||
const vaultStructure = getObjStructure(versionedData)
|
||||
raven.captureMessage('MetaMask - Empty vault found - recovered from diskStore', {
|
||||
sentry.captureMessage('MetaMask - Empty vault found - recovered from diskStore', {
|
||||
// "extra" key is required by Sentry
|
||||
extra: { vaultStructure },
|
||||
})
|
||||
} else {
|
||||
// unable to recover, clear state
|
||||
versionedData = migrator.generateInitialState(firstTimeState)
|
||||
raven.captureMessage('MetaMask - Empty vault found - unable to recover')
|
||||
sentry.captureMessage('MetaMask - Empty vault found - unable to recover')
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,7 +210,7 @@ async function loadStateFromPersistence () {
|
||||
migrator.on('error', (err) => {
|
||||
// get vault structure without secrets
|
||||
const vaultStructure = getObjStructure(versionedData)
|
||||
raven.captureException(err, {
|
||||
sentry.captureException(err, {
|
||||
// "extra" key is required by Sentry
|
||||
extra: { vaultStructure },
|
||||
})
|
||||
@ -258,7 +256,8 @@ function setupController (initState, initLangCode) {
|
||||
showUnconfirmedMessage: triggerUi,
|
||||
unlockAccountMessage: triggerUi,
|
||||
showUnapprovedTx: triggerUi,
|
||||
showWatchAssetUi: showWatchAssetUi,
|
||||
openPopup: openPopup,
|
||||
closePopup: notificationManager.closePopup.bind(notificationManager),
|
||||
// initial state
|
||||
initState,
|
||||
// initial locale code
|
||||
@ -269,17 +268,15 @@ function setupController (initState, initLangCode) {
|
||||
})
|
||||
global.metamaskController = controller
|
||||
|
||||
controller.networkController.on('networkDidChange', () => {
|
||||
ipfsHandle && ipfsHandle.remove()
|
||||
ipfsHandle = ipfsContent(controller.networkController.providerStore.getState())
|
||||
})
|
||||
const provider = controller.provider
|
||||
setupEnsIpfsResolver({ provider })
|
||||
|
||||
// report failed transactions to Sentry
|
||||
controller.txController.on(`tx:status-update`, (txId, status) => {
|
||||
if (status !== 'failed') return
|
||||
const txMeta = controller.txController.txStateManager.getTx(txId)
|
||||
try {
|
||||
reportFailedTxToSentry({ raven, txMeta })
|
||||
reportFailedTxToSentry({ sentry, txMeta })
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
@ -459,7 +456,7 @@ function triggerUi () {
|
||||
* Opens the browser popup for user confirmation of watchAsset
|
||||
* then it waits until user interact with the UI
|
||||
*/
|
||||
function showWatchAssetUi () {
|
||||
function openPopup () {
|
||||
triggerUi()
|
||||
return new Promise(
|
||||
(resolve) => {
|
||||
|
@ -7,10 +7,12 @@ const PongStream = require('ping-pong-stream/pong')
|
||||
const ObjectMultiplex = require('obj-multiplex')
|
||||
const extension = require('extensionizer')
|
||||
const PortStream = require('extension-port-stream')
|
||||
const TransformStream = require('stream').Transform
|
||||
|
||||
const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString()
|
||||
const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n'
|
||||
const inpageBundle = inpageContent + inpageSuffix
|
||||
let isEnabled = false
|
||||
|
||||
// Eventually this streaming injection could be replaced with:
|
||||
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction
|
||||
@ -20,24 +22,27 @@ const inpageBundle = inpageContent + inpageSuffix
|
||||
// MetaMask will be much faster loading and performant on Firefox.
|
||||
|
||||
if (shouldInjectWeb3()) {
|
||||
setupInjection()
|
||||
injectScript(inpageBundle)
|
||||
setupStreams()
|
||||
listenForProviderRequest()
|
||||
checkPrivacyMode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a script tag that injects inpage.js
|
||||
* Injects a script tag into the current document
|
||||
*
|
||||
* @param {string} content - Code to be executed in the current document
|
||||
*/
|
||||
function setupInjection () {
|
||||
function injectScript (content) {
|
||||
try {
|
||||
// inject in-page script
|
||||
var scriptTag = document.createElement('script')
|
||||
scriptTag.textContent = inpageBundle
|
||||
scriptTag.onload = function () { this.parentNode.removeChild(this) }
|
||||
var container = document.head || document.documentElement
|
||||
// append as first child
|
||||
const container = document.head || document.documentElement
|
||||
const scriptTag = document.createElement('script')
|
||||
scriptTag.setAttribute('async', false)
|
||||
scriptTag.textContent = content
|
||||
container.insertBefore(scriptTag, container.children[0])
|
||||
container.removeChild(scriptTag)
|
||||
} catch (e) {
|
||||
console.error('Metamask injection failed.', e)
|
||||
console.error('MetaMask script injection failed', e)
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,10 +59,22 @@ function setupStreams () {
|
||||
const pluginPort = extension.runtime.connect({ name: 'contentscript' })
|
||||
const pluginStream = new PortStream(pluginPort)
|
||||
|
||||
// Filter out selectedAddress until this origin is enabled
|
||||
const approvalTransform = new TransformStream({
|
||||
objectMode: true,
|
||||
transform: (data, _, done) => {
|
||||
if (typeof data === 'object' && data.name && data.name === 'publicConfig' && !isEnabled) {
|
||||
data.data.selectedAddress = undefined
|
||||
}
|
||||
done(null, { ...data })
|
||||
},
|
||||
})
|
||||
|
||||
// forward communication plugin->inpage
|
||||
pump(
|
||||
pageStream,
|
||||
pluginStream,
|
||||
approvalTransform,
|
||||
pageStream,
|
||||
(err) => logStreamDisconnectWarning('MetaMask Contentscript Forwarding', err)
|
||||
)
|
||||
@ -97,6 +114,69 @@ function setupStreams () {
|
||||
mux.ignoreStream('publicConfig')
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes listeners for requests to fully-enable the provider from the dapp context
|
||||
* and for full-provider approvals and rejections from the background script context. Dapps
|
||||
* should not post messages directly and should instead call provider.enable(), which
|
||||
* handles posting these messages internally.
|
||||
*/
|
||||
function listenForProviderRequest () {
|
||||
window.addEventListener('message', ({ source, data }) => {
|
||||
if (source !== window || !data || !data.type) { return }
|
||||
switch (data.type) {
|
||||
case 'ETHEREUM_ENABLE_PROVIDER':
|
||||
extension.runtime.sendMessage({
|
||||
action: 'init-provider-request',
|
||||
force: data.force,
|
||||
origin: source.location.hostname,
|
||||
siteImage: getSiteIcon(source),
|
||||
siteTitle: getSiteName(source),
|
||||
})
|
||||
break
|
||||
case 'ETHEREUM_IS_APPROVED':
|
||||
extension.runtime.sendMessage({
|
||||
action: 'init-is-approved',
|
||||
origin: source.location.hostname,
|
||||
})
|
||||
break
|
||||
case 'METAMASK_IS_UNLOCKED':
|
||||
extension.runtime.sendMessage({
|
||||
action: 'init-is-unlocked',
|
||||
})
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
extension.runtime.onMessage.addListener(({ action = '', isApproved, caching, isUnlocked }) => {
|
||||
switch (action) {
|
||||
case 'approve-provider-request':
|
||||
isEnabled = true
|
||||
injectScript(`window.dispatchEvent(new CustomEvent('ethereumprovider', { detail: {}}))`)
|
||||
break
|
||||
case 'reject-provider-request':
|
||||
injectScript(`window.dispatchEvent(new CustomEvent('ethereumprovider', { detail: { error: 'User rejected provider access' }}))`)
|
||||
break
|
||||
case 'answer-is-approved':
|
||||
injectScript(`window.dispatchEvent(new CustomEvent('ethereumisapproved', { detail: { isApproved: ${isApproved}, caching: ${caching}}}))`)
|
||||
break
|
||||
case 'answer-is-unlocked':
|
||||
injectScript(`window.dispatchEvent(new CustomEvent('metamaskisunlocked', { detail: { isUnlocked: ${isUnlocked}}}))`)
|
||||
break
|
||||
case 'metamask-set-locked':
|
||||
isEnabled = false
|
||||
injectScript(`window.dispatchEvent(new CustomEvent('metamasksetlocked', { detail: {}}))`)
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if MetaMask is currently operating in "privacy mode", meaning
|
||||
* dapps must call ethereum.enable in order to access user accounts
|
||||
*/
|
||||
function checkPrivacyMode () {
|
||||
extension.runtime.sendMessage({ action: 'init-privacy-request' })
|
||||
}
|
||||
|
||||
/**
|
||||
* Error handler for page to plugin stream disconnections
|
||||
@ -210,3 +290,31 @@ function redirectToPhishingWarning () {
|
||||
href: window.location.href,
|
||||
})}`
|
||||
}
|
||||
|
||||
function getSiteName (window) {
|
||||
const document = window.document
|
||||
const siteName = document.querySelector('head > meta[property="og:site_name"]')
|
||||
if (siteName) {
|
||||
return siteName.content
|
||||
}
|
||||
|
||||
return document.title
|
||||
}
|
||||
|
||||
function getSiteIcon (window) {
|
||||
const document = window.document
|
||||
|
||||
// Use the site's favicon if it exists
|
||||
const shortcutIcon = document.querySelector('head > link[rel="shortcut icon"]')
|
||||
if (shortcutIcon) {
|
||||
return shortcutIcon.href
|
||||
}
|
||||
|
||||
// Search through available icons in no particular order
|
||||
const icon = Array.from(document.querySelectorAll('head > link[rel="icon"]')).find((icon) => Boolean(icon.href))
|
||||
if (icon) {
|
||||
return icon.href
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
@ -83,8 +83,25 @@ class BlacklistController {
|
||||
*
|
||||
*/
|
||||
async updatePhishingList () {
|
||||
const response = await fetch('https://api.infura.io/v2/blacklist')
|
||||
const phishing = await response.json()
|
||||
// make request
|
||||
let response
|
||||
try {
|
||||
response = await fetch('https://api.infura.io/v2/blacklist')
|
||||
} catch (err) {
|
||||
log.error(new Error(`BlacklistController - failed to fetch blacklist:\n${err.stack}`))
|
||||
return
|
||||
}
|
||||
// parse response
|
||||
let rawResponse
|
||||
let phishing
|
||||
try {
|
||||
const rawResponse = await response.text()
|
||||
phishing = JSON.parse(rawResponse)
|
||||
} catch (err) {
|
||||
log.error(new Error(`BlacklistController - failed to parse blacklist:\n${rawResponse}`))
|
||||
return
|
||||
}
|
||||
// update current blacklist
|
||||
this.store.updateState({ phishing })
|
||||
this._setupPhishingDetector(phishing)
|
||||
return phishing
|
||||
@ -97,9 +114,9 @@ class BlacklistController {
|
||||
*/
|
||||
scheduleUpdates () {
|
||||
if (this._phishingUpdateIntervalRef) return
|
||||
this.updatePhishingList().catch(log.warn)
|
||||
this.updatePhishingList()
|
||||
this._phishingUpdateIntervalRef = setInterval(() => {
|
||||
this.updatePhishingList().catch(log.warn)
|
||||
this.updatePhishingList()
|
||||
}, POLLING_INTERVAL)
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ class CurrencyController {
|
||||
* since midnight of January 1, 1970
|
||||
* @property {number} conversionInterval The id of the interval created by the scheduleConversionInterval method.
|
||||
* Used to clear an existing interval on subsequent calls of that method.
|
||||
* @property {string} nativeCurrency The ticker/symbol of the native chain currency
|
||||
*
|
||||
*/
|
||||
constructor (opts = {}) {
|
||||
@ -28,6 +29,7 @@ class CurrencyController {
|
||||
currentCurrency: 'usd',
|
||||
conversionRate: 0,
|
||||
conversionDate: 'N/A',
|
||||
nativeCurrency: 'ETH',
|
||||
}, opts.initState)
|
||||
this.store = new ObservableStore(initState)
|
||||
}
|
||||
@ -36,6 +38,29 @@ class CurrencyController {
|
||||
// PUBLIC METHODS
|
||||
//
|
||||
|
||||
/**
|
||||
* A getter for the nativeCurrency property
|
||||
*
|
||||
* @returns {string} A 2-4 character shorthand that describes the specific currency
|
||||
*
|
||||
*/
|
||||
getNativeCurrency () {
|
||||
return this.store.getState().nativeCurrency
|
||||
}
|
||||
|
||||
/**
|
||||
* A setter for the nativeCurrency property
|
||||
*
|
||||
* @param {string} nativeCurrency The new currency to set as the nativeCurrency in the store
|
||||
*
|
||||
*/
|
||||
setNativeCurrency (nativeCurrency) {
|
||||
this.store.updateState({
|
||||
nativeCurrency,
|
||||
ticker: nativeCurrency,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for the currentCurrency property
|
||||
*
|
||||
@ -104,19 +129,62 @@ class CurrencyController {
|
||||
*
|
||||
*/
|
||||
async updateConversionRate () {
|
||||
let currentCurrency
|
||||
let currentCurrency, nativeCurrency
|
||||
try {
|
||||
currentCurrency = this.getCurrentCurrency()
|
||||
const response = await fetch(`https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`)
|
||||
const parsedResponse = await response.json()
|
||||
nativeCurrency = this.getNativeCurrency()
|
||||
// select api
|
||||
let apiUrl
|
||||
if (nativeCurrency === 'ETH') {
|
||||
// ETH
|
||||
apiUrl = `https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`
|
||||
} else {
|
||||
// ETC
|
||||
apiUrl = `https://min-api.cryptocompare.com/data/price?fsym=${nativeCurrency.toUpperCase()}&tsyms=${currentCurrency.toUpperCase()}`
|
||||
}
|
||||
// attempt request
|
||||
let response
|
||||
try {
|
||||
response = await fetch(apiUrl)
|
||||
} catch (err) {
|
||||
log.error(new Error(`CurrencyController - Failed to request currency from Infura:\n${err.stack}`))
|
||||
return
|
||||
}
|
||||
// parse response
|
||||
let rawResponse
|
||||
let parsedResponse
|
||||
try {
|
||||
rawResponse = await response.text()
|
||||
parsedResponse = JSON.parse(rawResponse)
|
||||
} catch (err) {
|
||||
log.error(new Error(`CurrencyController - Failed to parse response "${rawResponse}"`))
|
||||
return
|
||||
}
|
||||
// set conversion rate
|
||||
if (nativeCurrency === 'ETH') {
|
||||
// ETH
|
||||
this.setConversionRate(Number(parsedResponse.bid))
|
||||
this.setConversionDate(Number(parsedResponse.timestamp))
|
||||
} catch (err) {
|
||||
log.warn(`MetaMask - Failed to query currency conversion:`, currentCurrency, err)
|
||||
} else {
|
||||
// ETC
|
||||
if (parsedResponse[currentCurrency.toUpperCase()]) {
|
||||
this.setConversionRate(Number(parsedResponse[currentCurrency.toUpperCase()]))
|
||||
this.setConversionDate(parseInt((new Date()).getTime() / 1000))
|
||||
} else {
|
||||
this.setConversionRate(0)
|
||||
this.setConversionDate('N/A')
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// reset current conversion rate
|
||||
log.warn(`MetaMask - Failed to query currency conversion:`, nativeCurrency, currentCurrency, err)
|
||||
this.setConversionRate(0)
|
||||
this.setConversionDate('N/A')
|
||||
// throw error
|
||||
log.error(new Error(`CurrencyController - Failed to query rate for currency "${currentCurrency}":\n${err.stack}`))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new poll, using setInterval, to periodically call updateConversionRate. The id of the interval is
|
||||
|
@ -1,4 +1,5 @@
|
||||
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
|
||||
const createScaffoldMiddleware = require('json-rpc-engine/src/createScaffoldMiddleware')
|
||||
const createBlockReRefMiddleware = require('eth-json-rpc-middleware/block-ref')
|
||||
const createRetryOnEmptyMiddleware = require('eth-json-rpc-middleware/retryOnEmpty')
|
||||
const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache')
|
||||
@ -16,6 +17,7 @@ function createInfuraClient ({ network }) {
|
||||
const blockTracker = new BlockTracker({ provider: infuraProvider })
|
||||
|
||||
const networkMiddleware = mergeMiddleware([
|
||||
createNetworkAndChainIdMiddleware({ network }),
|
||||
createBlockCacheMiddleware({ blockTracker }),
|
||||
createInflightMiddleware(),
|
||||
createBlockReRefMiddleware({ blockTracker, provider: infuraProvider }),
|
||||
@ -25,3 +27,34 @@ function createInfuraClient ({ network }) {
|
||||
])
|
||||
return { networkMiddleware, blockTracker }
|
||||
}
|
||||
|
||||
function createNetworkAndChainIdMiddleware({ network }) {
|
||||
let chainId
|
||||
let netId
|
||||
|
||||
switch (network) {
|
||||
case 'mainnet':
|
||||
netId = '1'
|
||||
chainId = '0x01'
|
||||
break
|
||||
case 'ropsten':
|
||||
netId = '3'
|
||||
chainId = '0x03'
|
||||
break
|
||||
case 'rinkeby':
|
||||
netId = '4'
|
||||
chainId = '0x04'
|
||||
break
|
||||
case 'kovan':
|
||||
netId = '42'
|
||||
chainId = '0x2a'
|
||||
break
|
||||
default:
|
||||
throw new Error(`createInfuraClient - unknown network "${network}"`)
|
||||
}
|
||||
|
||||
return createScaffoldMiddleware({
|
||||
eth_chainId: chainId,
|
||||
net_version: netId,
|
||||
})
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ function createMetamaskMiddleware ({
|
||||
processTransaction,
|
||||
processEthSignMessage,
|
||||
processTypedMessage,
|
||||
processTypedMessageV3,
|
||||
processPersonalMessage,
|
||||
getPendingNonce,
|
||||
}) {
|
||||
@ -25,6 +26,7 @@ function createMetamaskMiddleware ({
|
||||
processTransaction,
|
||||
processEthSignMessage,
|
||||
processTypedMessage,
|
||||
processTypedMessageV3,
|
||||
processPersonalMessage,
|
||||
}),
|
||||
createPendingNonceMiddleware({ getPendingNonce }),
|
||||
|
@ -11,6 +11,8 @@ const createInfuraClient = require('./createInfuraClient')
|
||||
const createJsonRpcClient = require('./createJsonRpcClient')
|
||||
const createLocalhostClient = require('./createLocalhostClient')
|
||||
const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy')
|
||||
const extend = require('extend')
|
||||
const networks = { networkList: {} }
|
||||
|
||||
const {
|
||||
ROPSTEN,
|
||||
@ -29,6 +31,10 @@ const defaultProviderConfig = {
|
||||
type: testMode ? RINKEBY : MAINNET,
|
||||
}
|
||||
|
||||
const defaultNetworkConfig = {
|
||||
ticker: 'ETH',
|
||||
}
|
||||
|
||||
module.exports = class NetworkController extends EventEmitter {
|
||||
|
||||
constructor (opts = {}) {
|
||||
@ -39,7 +45,8 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
// create stores
|
||||
this.providerStore = new ObservableStore(providerConfig)
|
||||
this.networkStore = new ObservableStore('loading')
|
||||
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore })
|
||||
this.networkConfig = new ObservableStore(defaultNetworkConfig)
|
||||
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore, settings: this.networkConfig })
|
||||
this.on('networkDidChange', this.lookupNetwork)
|
||||
// provider and block tracker
|
||||
this._provider = null
|
||||
@ -51,8 +58,8 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
|
||||
initializeProvider (providerParams) {
|
||||
this._baseProviderParams = providerParams
|
||||
const { type, rpcTarget } = this.providerStore.getState()
|
||||
this._configureProvider({ type, rpcTarget })
|
||||
const { type, rpcTarget, chainId, ticker, nickname } = this.providerStore.getState()
|
||||
this._configureProvider({ type, rpcTarget, chainId, ticker, nickname })
|
||||
this.lookupNetwork()
|
||||
}
|
||||
|
||||
@ -72,7 +79,20 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
return this.networkStore.getState()
|
||||
}
|
||||
|
||||
setNetworkState (network) {
|
||||
getNetworkConfig () {
|
||||
return this.networkConfig.getState()
|
||||
}
|
||||
|
||||
setNetworkState (network, type) {
|
||||
if (network === 'loading') {
|
||||
return this.networkStore.putState(network)
|
||||
}
|
||||
|
||||
// type must be defined
|
||||
if (!type) {
|
||||
return
|
||||
}
|
||||
network = networks.networkList[type] && networks.networkList[type].chainId ? networks.networkList[type].chainId : network
|
||||
return this.networkStore.putState(network)
|
||||
}
|
||||
|
||||
@ -85,18 +105,22 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
if (!this._provider) {
|
||||
return log.warn('NetworkController - lookupNetwork aborted due to missing provider')
|
||||
}
|
||||
var { type } = this.providerStore.getState()
|
||||
const ethQuery = new EthQuery(this._provider)
|
||||
ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
|
||||
if (err) return this.setNetworkState('loading')
|
||||
log.info('web3.getNetwork returned ' + network)
|
||||
this.setNetworkState(network)
|
||||
this.setNetworkState(network, type)
|
||||
})
|
||||
}
|
||||
|
||||
setRpcTarget (rpcTarget) {
|
||||
setRpcTarget (rpcTarget, chainId, ticker = 'ETH', nickname = '') {
|
||||
const providerConfig = {
|
||||
type: 'rpc',
|
||||
rpcTarget,
|
||||
chainId,
|
||||
ticker,
|
||||
nickname,
|
||||
}
|
||||
this.providerConfig = providerConfig
|
||||
}
|
||||
@ -132,7 +156,7 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
}
|
||||
|
||||
_configureProvider (opts) {
|
||||
const { type, rpcTarget } = opts
|
||||
const { type, rpcTarget, chainId, ticker, nickname } = opts
|
||||
// infura type-based endpoints
|
||||
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
|
||||
if (isInfura) {
|
||||
@ -142,7 +166,7 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
this._configureLocalhostProvider()
|
||||
// url-based rpc endpoints
|
||||
} else if (type === 'rpc') {
|
||||
this._configureStandardProvider({ rpcUrl: rpcTarget })
|
||||
this._configureStandardProvider({ rpcUrl: rpcTarget, chainId, ticker, nickname })
|
||||
} else {
|
||||
throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`)
|
||||
}
|
||||
@ -152,6 +176,11 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
log.info('NetworkController - configureInfuraProvider', type)
|
||||
const networkClient = createInfuraClient({ network: type })
|
||||
this._setNetworkClient(networkClient)
|
||||
// setup networkConfig
|
||||
var settings = {
|
||||
ticker: 'ETH',
|
||||
}
|
||||
this.networkConfig.putState(settings)
|
||||
}
|
||||
|
||||
_configureLocalhostProvider () {
|
||||
@ -160,9 +189,22 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
this._setNetworkClient(networkClient)
|
||||
}
|
||||
|
||||
_configureStandardProvider ({ rpcUrl }) {
|
||||
_configureStandardProvider ({ rpcUrl, chainId, ticker, nickname }) {
|
||||
log.info('NetworkController - configureStandardProvider', rpcUrl)
|
||||
const networkClient = createJsonRpcClient({ rpcUrl })
|
||||
// hack to add a 'rpc' network with chainId
|
||||
networks.networkList['rpc'] = {
|
||||
chainId: chainId,
|
||||
rpcUrl,
|
||||
ticker: ticker || 'ETH',
|
||||
nickname,
|
||||
}
|
||||
// setup networkConfig
|
||||
var settings = {
|
||||
network: chainId,
|
||||
}
|
||||
settings = extend(settings, networks.networkList['rpc'])
|
||||
this.networkConfig.putState(settings)
|
||||
this._setNetworkClient(networkClient)
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ class PreferencesController {
|
||||
*/
|
||||
constructor (opts = {}) {
|
||||
const initState = extend({
|
||||
frequentRpcList: [],
|
||||
frequentRpcListDetail: [],
|
||||
currentAccountTab: 'history',
|
||||
accountTokens: {},
|
||||
assetImages: {},
|
||||
@ -39,14 +39,14 @@ class PreferencesController {
|
||||
seedWords: null,
|
||||
forgottenPassword: false,
|
||||
preferences: {
|
||||
useETHAsPrimaryCurrency: true,
|
||||
useNativeCurrencyAsPrimaryCurrency: true,
|
||||
},
|
||||
}, opts.initState)
|
||||
|
||||
this.diagnostics = opts.diagnostics
|
||||
this.network = opts.network
|
||||
this.store = new ObservableStore(initState)
|
||||
this.showWatchAssetUi = opts.showWatchAssetUi
|
||||
this.openPopup = opts.openPopup
|
||||
this._subscribeProviderType()
|
||||
}
|
||||
// PUBLIC METHODS
|
||||
@ -392,19 +392,22 @@ class PreferencesController {
|
||||
* Adds custom RPC url to state.
|
||||
*
|
||||
* @param {string} url The RPC url to add to frequentRpcList.
|
||||
* @param {number} chainId Optional chainId of the selected network.
|
||||
* @param {string} ticker Optional ticker symbol of the selected network.
|
||||
* @param {string} nickname Optional nickname of the selected network.
|
||||
* @returns {Promise<array>} Promise resolving to updated frequentRpcList.
|
||||
*
|
||||
*/
|
||||
addToFrequentRpcList (url) {
|
||||
const rpcList = this.getFrequentRpcList()
|
||||
const index = rpcList.findIndex((element) => { return element === url })
|
||||
addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '') {
|
||||
const rpcList = this.getFrequentRpcListDetail()
|
||||
const index = rpcList.findIndex((element) => { return element.rpcUrl === url })
|
||||
if (index !== -1) {
|
||||
rpcList.splice(index, 1)
|
||||
}
|
||||
if (url !== 'http://localhost:8545') {
|
||||
rpcList.push(url)
|
||||
rpcList.push({ rpcUrl: url, chainId, ticker, nickname })
|
||||
}
|
||||
this.store.updateState({ frequentRpcList: rpcList })
|
||||
this.store.updateState({ frequentRpcListDetail: rpcList })
|
||||
return Promise.resolve(rpcList)
|
||||
}
|
||||
|
||||
@ -416,23 +419,23 @@ class PreferencesController {
|
||||
*
|
||||
*/
|
||||
removeFromFrequentRpcList (url) {
|
||||
const rpcList = this.getFrequentRpcList()
|
||||
const index = rpcList.findIndex((element) => { return element === url })
|
||||
const rpcList = this.getFrequentRpcListDetail()
|
||||
const index = rpcList.findIndex((element) => { return element.rpcUrl === url })
|
||||
if (index !== -1) {
|
||||
rpcList.splice(index, 1)
|
||||
}
|
||||
this.store.updateState({ frequentRpcList: rpcList })
|
||||
this.store.updateState({ frequentRpcListDetail: rpcList })
|
||||
return Promise.resolve(rpcList)
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the `frequentRpcList` property.
|
||||
* Getter for the `frequentRpcListDetail` property.
|
||||
*
|
||||
* @returns {array<string>} An array of one or two rpc urls.
|
||||
* @returns {array<array>} An array of rpc urls.
|
||||
*
|
||||
*/
|
||||
getFrequentRpcList () {
|
||||
return this.store.getState().frequentRpcList
|
||||
getFrequentRpcListDetail () {
|
||||
return this.store.getState().frequentRpcListDetail
|
||||
}
|
||||
|
||||
/**
|
||||
@ -564,7 +567,7 @@ class PreferencesController {
|
||||
}
|
||||
const tokenOpts = { rawAddress, decimals, symbol, image }
|
||||
this.addSuggestedERC20Asset(tokenOpts)
|
||||
return this.showWatchAssetUi().then(() => {
|
||||
return this.openPopup().then(() => {
|
||||
const tokenAddresses = this.getTokens().filter(token => token.address === normalizeAddress(rawAddress))
|
||||
return tokenAddresses.length > 0
|
||||
})
|
||||
@ -580,8 +583,8 @@ class PreferencesController {
|
||||
*/
|
||||
_validateERC20AssetParams (opts) {
|
||||
const { rawAddress, symbol, decimals } = opts
|
||||
if (!rawAddress || !symbol || !decimals) throw new Error(`Cannot suggest token without address, symbol, and decimals`)
|
||||
if (!(symbol.length < 6)) throw new Error(`Invalid symbol ${symbol} more than five characters`)
|
||||
if (!rawAddress || !symbol || typeof decimals === 'undefined') throw new Error(`Cannot suggest token without address, symbol, and decimals`)
|
||||
if (!(symbol.length < 7)) throw new Error(`Invalid symbol ${symbol} more than six characters`)
|
||||
const numDecimals = parseInt(decimals, 10)
|
||||
if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) {
|
||||
throw new Error(`Invalid decimals ${decimals} must be at least 0, and not over 36`)
|
||||
|
152
app/scripts/controllers/provider-approval.js
Normal file
152
app/scripts/controllers/provider-approval.js
Normal file
@ -0,0 +1,152 @@
|
||||
const ObservableStore = require('obs-store')
|
||||
|
||||
/**
|
||||
* A controller that services user-approved requests for a full Ethereum provider API
|
||||
*/
|
||||
class ProviderApprovalController {
|
||||
/**
|
||||
* Determines if caching is enabled
|
||||
*/
|
||||
caching = true
|
||||
|
||||
/**
|
||||
* Creates a ProviderApprovalController
|
||||
*
|
||||
* @param {Object} [config] - Options to configure controller
|
||||
*/
|
||||
constructor ({ closePopup, keyringController, openPopup, platform, preferencesController, publicConfigStore } = {}) {
|
||||
this.approvedOrigins = {}
|
||||
this.closePopup = closePopup
|
||||
this.keyringController = keyringController
|
||||
this.openPopup = openPopup
|
||||
this.platform = platform
|
||||
this.preferencesController = preferencesController
|
||||
this.publicConfigStore = publicConfigStore
|
||||
this.store = new ObservableStore()
|
||||
|
||||
if (platform && platform.addMessageListener) {
|
||||
platform.addMessageListener(({ action = '', force, origin, siteTitle, siteImage }) => {
|
||||
switch (action) {
|
||||
case 'init-provider-request':
|
||||
this._handleProviderRequest(origin, siteTitle, siteImage, force)
|
||||
break
|
||||
case 'init-is-approved':
|
||||
this._handleIsApproved(origin)
|
||||
break
|
||||
case 'init-is-unlocked':
|
||||
this._handleIsUnlocked()
|
||||
break
|
||||
case 'init-privacy-request':
|
||||
this._handlePrivacyRequest()
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a tab requests access to a full Ethereum provider API
|
||||
*
|
||||
* @param {string} origin - Origin of the window requesting full provider access
|
||||
* @param {string} siteTitle - The title of the document requesting full provider access
|
||||
* @param {string} siteImage - The icon of the window requesting full provider access
|
||||
*/
|
||||
_handleProviderRequest (origin, siteTitle, siteImage, force) {
|
||||
this.store.updateState({ providerRequests: [{ origin, siteTitle, siteImage }] })
|
||||
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
||||
if (!force && this.approvedOrigins[origin] && this.caching && isUnlocked) {
|
||||
this.approveProviderRequest(origin)
|
||||
return
|
||||
}
|
||||
this.openPopup && this.openPopup()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a tab to determine if an origin has been approved in the past
|
||||
*
|
||||
* @param {string} origin - Origin of the window
|
||||
*/
|
||||
_handleIsApproved (origin) {
|
||||
this.platform && this.platform.sendMessage({
|
||||
action: 'answer-is-approved',
|
||||
isApproved: this.approvedOrigins[origin] && this.caching,
|
||||
caching: this.caching,
|
||||
}, { active: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a tab to determine if MetaMask is currently locked or unlocked
|
||||
*/
|
||||
_handleIsUnlocked () {
|
||||
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
||||
this.platform && this.platform.sendMessage({ action: 'answer-is-unlocked', isUnlocked }, { active: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to check privacy mode; if privacy mode is off, this will automatically enable the provider (legacy behavior)
|
||||
*/
|
||||
_handlePrivacyRequest () {
|
||||
const privacyMode = this.preferencesController.getFeatureFlags().privacyMode
|
||||
if (!privacyMode) {
|
||||
this.platform && this.platform.sendMessage({ action: 'approve-provider-request' }, { active: true })
|
||||
this.publicConfigStore.emit('update', this.publicConfigStore.getState())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user approves access to a full Ethereum provider API
|
||||
*
|
||||
* @param {string} origin - Origin of the target window to approve provider access
|
||||
*/
|
||||
approveProviderRequest (origin) {
|
||||
this.closePopup && this.closePopup()
|
||||
const requests = this.store.getState().providerRequests || []
|
||||
this.platform && this.platform.sendMessage({ action: 'approve-provider-request' }, { active: true })
|
||||
this.publicConfigStore.emit('update', this.publicConfigStore.getState())
|
||||
const providerRequests = requests.filter(request => request.origin !== origin)
|
||||
this.store.updateState({ providerRequests })
|
||||
this.approvedOrigins[origin] = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a tab rejects access to a full Ethereum provider API
|
||||
*
|
||||
* @param {string} origin - Origin of the target window to reject provider access
|
||||
*/
|
||||
rejectProviderRequest (origin) {
|
||||
this.closePopup && this.closePopup()
|
||||
const requests = this.store.getState().providerRequests || []
|
||||
this.platform && this.platform.sendMessage({ action: 'reject-provider-request' }, { active: true })
|
||||
const providerRequests = requests.filter(request => request.origin !== origin)
|
||||
this.store.updateState({ providerRequests })
|
||||
delete this.approvedOrigins[origin]
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any cached approvals for user-approved origins
|
||||
*/
|
||||
clearApprovedOrigins () {
|
||||
this.approvedOrigins = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given origin should have accounts exposed
|
||||
*
|
||||
* @param {string} origin - Domain origin to check for approval status
|
||||
* @returns {boolean} - True if the origin has been approved
|
||||
*/
|
||||
shouldExposeAccounts (origin) {
|
||||
const privacyMode = this.preferencesController.getFeatureFlags().privacyMode
|
||||
return !privacyMode || this.approvedOrigins[origin]
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells all tabs that MetaMask is now locked. This is primarily used to set
|
||||
* internal flags in the contentscript and inpage script.
|
||||
*/
|
||||
setLocked () {
|
||||
this.platform.sendMessage({ action: 'metamask-set-locked' })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ProviderApprovalController
|
@ -1,5 +1,5 @@
|
||||
const ObservableStore = require('obs-store')
|
||||
const { warn } = require('loglevel')
|
||||
const log = require('loglevel')
|
||||
|
||||
// By default, poll every 3 minutes
|
||||
const DEFAULT_INTERVAL = 180 * 1000
|
||||
@ -26,8 +26,11 @@ class TokenRatesController {
|
||||
async updateExchangeRates () {
|
||||
if (!this.isActive) { return }
|
||||
const contractExchangeRates = {}
|
||||
for (const i in this._tokens) {
|
||||
const address = this._tokens[i].address
|
||||
// copy array to ensure its not modified during iteration
|
||||
const tokens = this._tokens.slice()
|
||||
for (const token of tokens) {
|
||||
if (!token) return log.error(`TokenRatesController - invalid tokens state:\n${JSON.stringify(tokens, null, 2)}`)
|
||||
const address = token.address
|
||||
contractExchangeRates[address] = await this.fetchExchangeRate(address)
|
||||
}
|
||||
this.store.putState({ contractExchangeRates })
|
||||
@ -44,7 +47,7 @@ class TokenRatesController {
|
||||
const json = await response.json()
|
||||
return json && json.length ? json[0].averagePrice : 0
|
||||
} catch (error) {
|
||||
warn(`MetaMask - TokenRatesController exchange rate fetch failed for ${address}.`, error)
|
||||
log.warn(`MetaMask - TokenRatesController exchange rate fetch failed for ${address}.`, error)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ const {
|
||||
const { addHexPrefix } = require('ethereumjs-util')
|
||||
const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
|
||||
|
||||
import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/app/constants/error-keys'
|
||||
|
||||
/**
|
||||
tx-gas-utils are gas utility methods for Transaction manager
|
||||
its passed ethquery
|
||||
@ -32,6 +34,7 @@ class TxGasUtil {
|
||||
} catch (err) {
|
||||
txMeta.simulationFails = {
|
||||
reason: err.message,
|
||||
errorKey: err.errorKey,
|
||||
}
|
||||
return txMeta
|
||||
}
|
||||
@ -56,24 +59,38 @@ class TxGasUtil {
|
||||
return txParams.gas
|
||||
}
|
||||
|
||||
// if recipient has no code, gas is 21k max:
|
||||
const recipient = txParams.to
|
||||
const hasRecipient = Boolean(recipient)
|
||||
let code
|
||||
if (recipient) code = await this.query.getCode(recipient)
|
||||
|
||||
if (hasRecipient && (!code || code === '0x')) {
|
||||
txParams.gas = SIMPLE_GAS_COST
|
||||
txMeta.simpleSend = true // Prevents buffer addition
|
||||
return SIMPLE_GAS_COST
|
||||
// see if we can set the gas based on the recipient
|
||||
if (hasRecipient) {
|
||||
const code = await this.query.getCode(recipient)
|
||||
// For an address with no code, geth will return '0x', and ganache-core v2.2.1 will return '0x0'
|
||||
const codeIsEmpty = !code || code === '0x' || code === '0x0'
|
||||
|
||||
if (codeIsEmpty) {
|
||||
// if there's data in the params, but there's no contract code, it's not a valid transaction
|
||||
if (txParams.data) {
|
||||
const err = new Error('TxGasUtil - Trying to call a function on a non-contract address')
|
||||
// set error key so ui can display localized error message
|
||||
err.errorKey = TRANSACTION_NO_CONTRACT_ERROR_KEY
|
||||
throw err
|
||||
}
|
||||
|
||||
// if not, fall back to block gasLimit
|
||||
// This is a standard ether simple send, gas requirement is exactly 21k
|
||||
txParams.gas = SIMPLE_GAS_COST
|
||||
// prevents buffer addition
|
||||
txMeta.simpleSend = true
|
||||
return SIMPLE_GAS_COST
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to block gasLimit
|
||||
const blockGasLimitBN = hexToBn(blockGasLimitHex)
|
||||
const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20)
|
||||
txParams.gas = bnToHex(saferGasLimitBN)
|
||||
|
||||
// run tx
|
||||
// estimate tx gas requirements
|
||||
return await this.query.estimateGas(txParams)
|
||||
}
|
||||
|
||||
|
@ -6,14 +6,21 @@ const LocalMessageDuplexStream = require('post-message-stream')
|
||||
const setupDappAutoReload = require('./lib/auto-reload.js')
|
||||
const MetamaskInpageProvider = require('metamask-inpage-provider')
|
||||
|
||||
let isEnabled = false
|
||||
let warned = false
|
||||
let providerHandle
|
||||
let isApprovedHandle
|
||||
let isUnlockedHandle
|
||||
|
||||
restoreContextAfterImports()
|
||||
|
||||
log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
|
||||
|
||||
console.warn('ATTENTION: In an effort to improve user privacy, MetaMask will ' +
|
||||
'stop exposing user accounts to dapps by default beginning November 2nd, 2018. ' +
|
||||
'Dapps should call provider.enable() in order to view and use accounts. Please see ' +
|
||||
'https://bit.ly/2QQHXvF for complete information and up-to-date example code.')
|
||||
console.warn('ATTENTION: In an effort to improve user privacy, MetaMask ' +
|
||||
'stopped exposing user accounts to dapps if "privacy mode" is enabled on ' +
|
||||
'November 2nd, 2018. Dapps should now call provider.enable() in order to view and use ' +
|
||||
'accounts. Please see https://bit.ly/2QQHXvF for complete information and up-to-date ' +
|
||||
'example code.')
|
||||
|
||||
//
|
||||
// setup plugin communication
|
||||
@ -27,26 +34,125 @@ var metamaskStream = new LocalMessageDuplexStream({
|
||||
|
||||
// compose the inpage provider
|
||||
var inpageProvider = new MetamaskInpageProvider(metamaskStream)
|
||||
|
||||
// set a high max listener count to avoid unnecesary warnings
|
||||
inpageProvider.setMaxListeners(100)
|
||||
|
||||
// Augment the provider with its enable method
|
||||
inpageProvider.enable = function (options = {}) {
|
||||
// set up a listener for when MetaMask is locked
|
||||
window.addEventListener('metamasksetlocked', () => {
|
||||
isEnabled = false
|
||||
})
|
||||
|
||||
// augment the provider with its enable method
|
||||
inpageProvider.enable = function ({ force } = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (options.mockRejection) {
|
||||
reject('User rejected account access')
|
||||
window.removeEventListener('ethereumprovider', providerHandle)
|
||||
providerHandle = ({ detail }) => {
|
||||
if (typeof detail.error !== 'undefined') {
|
||||
reject(detail.error)
|
||||
} else {
|
||||
// wait for the publicConfig store to populate with an account
|
||||
const publicConfig = new Promise((resolve) => {
|
||||
const { selectedAddress } = inpageProvider.publicConfigStore.getState()
|
||||
inpageProvider._metamask.isUnlocked().then(unlocked => {
|
||||
if (!unlocked || selectedAddress) {
|
||||
resolve()
|
||||
} else {
|
||||
inpageProvider.publicConfigStore.on('update', ({ selectedAddress }) => {
|
||||
selectedAddress && resolve()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// wait for the background to update with an account
|
||||
const ethAccounts = new Promise((resolveAccounts, rejectAccounts) => {
|
||||
inpageProvider.sendAsync({ method: 'eth_accounts', params: [] }, (error, response) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
rejectAccounts(error)
|
||||
} else {
|
||||
resolve(response.result)
|
||||
resolveAccounts(response.result)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Promise.all([ethAccounts, publicConfig])
|
||||
.then(([selectedAddress]) => {
|
||||
isEnabled = true
|
||||
resolve(selectedAddress)
|
||||
})
|
||||
.catch(reject)
|
||||
}
|
||||
}
|
||||
window.addEventListener('ethereumprovider', providerHandle)
|
||||
window.postMessage({ type: 'ETHEREUM_ENABLE_PROVIDER', force }, '*')
|
||||
})
|
||||
}
|
||||
|
||||
// add metamask-specific convenience methods
|
||||
inpageProvider._metamask = new Proxy({
|
||||
/**
|
||||
* Determines if this domain is currently enabled
|
||||
*
|
||||
* @returns {boolean} - true if this domain is currently enabled
|
||||
*/
|
||||
isEnabled: function () {
|
||||
return isEnabled
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if this domain has been previously approved
|
||||
*
|
||||
* @returns {Promise<boolean>} - Promise resolving to true if this domain has been previously approved
|
||||
*/
|
||||
isApproved: function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
window.removeEventListener('ethereumisapproved', isApprovedHandle)
|
||||
isApprovedHandle = ({ detail }) => {
|
||||
if (typeof detail.error !== 'undefined') {
|
||||
reject(detail.error)
|
||||
} else {
|
||||
if (detail.caching) {
|
||||
resolve(!!detail.isApproved)
|
||||
} else {
|
||||
resolve(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
window.addEventListener('ethereumisapproved', isApprovedHandle)
|
||||
window.postMessage({ type: 'ETHEREUM_IS_APPROVED' }, '*')
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if MetaMask is unlocked by the user
|
||||
*
|
||||
* @returns {Promise<boolean>} - Promise resolving to true if MetaMask is currently unlocked
|
||||
*/
|
||||
isUnlocked: function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
window.removeEventListener('metamaskisunlocked', isUnlockedHandle)
|
||||
isUnlockedHandle = ({ detail }) => {
|
||||
if (typeof detail.error !== 'undefined') {
|
||||
reject(detail.error)
|
||||
} else {
|
||||
resolve(!!detail.isUnlocked)
|
||||
}
|
||||
}
|
||||
window.addEventListener('metamaskisunlocked', isUnlockedHandle)
|
||||
window.postMessage({ type: 'METAMASK_IS_UNLOCKED' }, '*')
|
||||
})
|
||||
},
|
||||
}, {
|
||||
get: function(obj, prop) {
|
||||
!warned && console.warn('Heads up! ethereum._metamask exposes methods that have ' +
|
||||
'not been standardized yet. This means that these methods may not be implemented ' +
|
||||
'in other dapp browsers and may be removed from MetaMask in the future.')
|
||||
warned = true
|
||||
return obj[prop]
|
||||
},
|
||||
})
|
||||
|
||||
// Work around for web3@1.0 deleting the bound `sendAsync` but not the unbound
|
||||
// `sendAsync` method on the prototype, causing `this` reference issues with drizzle
|
||||
const proxiedInpageProvider = new Proxy(inpageProvider, {
|
||||
@ -57,6 +163,19 @@ const proxiedInpageProvider = new Proxy(inpageProvider, {
|
||||
|
||||
window.ethereum = proxiedInpageProvider
|
||||
|
||||
// detect eth_requestAccounts and pipe to enable for now
|
||||
function detectAccountRequest(method) {
|
||||
const originalMethod = inpageProvider[method]
|
||||
inpageProvider[method] = function ({ method }) {
|
||||
if (method === 'eth_requestAccounts') {
|
||||
return window.ethereum.enable()
|
||||
}
|
||||
return originalMethod.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
detectAccountRequest('send')
|
||||
detectAccountRequest('sendAsync')
|
||||
|
||||
//
|
||||
// setup web3
|
||||
//
|
||||
|
54
app/scripts/lib/ens-ipfs/resolver.js
Normal file
54
app/scripts/lib/ens-ipfs/resolver.js
Normal file
@ -0,0 +1,54 @@
|
||||
const namehash = require('eth-ens-namehash')
|
||||
const multihash = require('multihashes')
|
||||
const Eth = require('ethjs-query')
|
||||
const EthContract = require('ethjs-contract')
|
||||
const registrarAbi = require('./contracts/registrar')
|
||||
const resolverAbi = require('./contracts/resolver')
|
||||
|
||||
module.exports = resolveEnsToIpfsContentId
|
||||
|
||||
|
||||
async function resolveEnsToIpfsContentId ({ provider, name }) {
|
||||
const eth = new Eth(provider)
|
||||
const hash = namehash.hash(name)
|
||||
const contract = new EthContract(eth)
|
||||
// lookup registrar
|
||||
const chainId = Number.parseInt(await eth.net_version(), 10)
|
||||
const registrarAddress = getRegistrarForChainId(chainId)
|
||||
if (!registrarAddress) {
|
||||
throw new Error(`EnsIpfsResolver - no known ens-ipfs registrar for chainId "${chainId}"`)
|
||||
}
|
||||
const Registrar = contract(registrarAbi).at(registrarAddress)
|
||||
// lookup resolver
|
||||
const resolverLookupResult = await Registrar.resolver(hash)
|
||||
const resolverAddress = resolverLookupResult[0]
|
||||
if (hexValueIsEmpty(resolverAddress)) {
|
||||
throw new Error(`EnsIpfsResolver - no resolver found for name "${name}"`)
|
||||
}
|
||||
const Resolver = contract(resolverAbi).at(resolverAddress)
|
||||
// lookup content id
|
||||
const contentLookupResult = await Resolver.content(hash)
|
||||
const contentHash = contentLookupResult[0]
|
||||
if (hexValueIsEmpty(contentHash)) {
|
||||
throw new Error(`EnsIpfsResolver - no content ID found for name "${name}"`)
|
||||
}
|
||||
const nonPrefixedHex = contentHash.slice(2)
|
||||
const buffer = multihash.fromHexString(nonPrefixedHex)
|
||||
const contentId = multihash.toB58String(multihash.encode(buffer, 'sha2-256'))
|
||||
return contentId
|
||||
}
|
||||
|
||||
function hexValueIsEmpty(value) {
|
||||
return [undefined, null, '0x', '0x0', '0x0000000000000000000000000000000000000000000000000000000000000000'].includes(value)
|
||||
}
|
||||
|
||||
function getRegistrarForChainId (chainId) {
|
||||
switch (chainId) {
|
||||
// mainnet
|
||||
case 1:
|
||||
return '0x314159265dd8dbb310642f98f50c066173c1259b'
|
||||
// ropsten
|
||||
case 3:
|
||||
return '0x112234455c3a32fd11230c42e7bccd4a84e02010'
|
||||
}
|
||||
}
|
63
app/scripts/lib/ens-ipfs/setup.js
Normal file
63
app/scripts/lib/ens-ipfs/setup.js
Normal file
@ -0,0 +1,63 @@
|
||||
const urlUtil = require('url')
|
||||
const extension = require('extensionizer')
|
||||
const resolveEnsToIpfsContentId = require('./resolver.js')
|
||||
|
||||
const supportedTopLevelDomains = ['eth']
|
||||
|
||||
module.exports = setupEnsIpfsResolver
|
||||
|
||||
function setupEnsIpfsResolver({ provider }) {
|
||||
|
||||
// install listener
|
||||
const urlPatterns = supportedTopLevelDomains.map(tld => `*://*.${tld}/*`)
|
||||
extension.webRequest.onErrorOccurred.addListener(webRequestDidFail, { urls: urlPatterns })
|
||||
|
||||
// return api object
|
||||
return {
|
||||
// uninstall listener
|
||||
remove () {
|
||||
extension.webRequest.onErrorOccurred.removeListener(webRequestDidFail)
|
||||
},
|
||||
}
|
||||
|
||||
async function webRequestDidFail (details) {
|
||||
const { tabId, url } = details
|
||||
// ignore requests that are not associated with tabs
|
||||
if (tabId === -1) return
|
||||
// parse ens name
|
||||
const urlData = urlUtil.parse(url)
|
||||
const { hostname: name, path, search } = urlData
|
||||
const domainParts = name.split('.')
|
||||
const topLevelDomain = domainParts[domainParts.length - 1]
|
||||
// if unsupported TLD, abort
|
||||
if (!supportedTopLevelDomains.includes(topLevelDomain)) return
|
||||
// otherwise attempt resolve
|
||||
attemptResolve({ tabId, name, path, search })
|
||||
}
|
||||
|
||||
async function attemptResolve({ tabId, name, path, search }) {
|
||||
extension.tabs.update(tabId, { url: `loading.html` })
|
||||
try {
|
||||
const ipfsContentId = await resolveEnsToIpfsContentId({ provider, name })
|
||||
let url = `https://gateway.ipfs.io/ipfs/${ipfsContentId}${path}${search || ''}`
|
||||
try {
|
||||
// check if ipfs gateway has result
|
||||
const response = await fetch(url, { method: 'HEAD' })
|
||||
// if failure, redirect to 404 page
|
||||
if (response.status !== 200) {
|
||||
extension.tabs.update(tabId, { url: '404.html' })
|
||||
return
|
||||
}
|
||||
// otherwise redirect to the correct page
|
||||
extension.tabs.update(tabId, { url })
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
// if HEAD fetch failed, redirect so user can see relevant error page
|
||||
extension.tabs.update(tabId, { url })
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
extension.tabs.update(tabId, { url: `error.html?name=${name}` })
|
||||
}
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
const extension = require('extensionizer')
|
||||
const resolver = require('./resolver.js')
|
||||
|
||||
module.exports = function (provider) {
|
||||
function ipfsContent (details) {
|
||||
const name = details.url.substring(7, details.url.length - 1)
|
||||
let clearTime = null
|
||||
if (/^.+\.eth$/.test(name) === false) return
|
||||
|
||||
extension.tabs.query({active: true}, tab => {
|
||||
extension.tabs.update(tab.id, { url: 'loading.html' })
|
||||
|
||||
clearTime = setTimeout(() => {
|
||||
return extension.tabs.update(tab.id, { url: '404.html' })
|
||||
}, 60000)
|
||||
|
||||
resolver.resolve(name, provider).then(ipfsHash => {
|
||||
clearTimeout(clearTime)
|
||||
let url = 'https://ipfs.infura.io/ipfs/' + ipfsHash
|
||||
return fetch(url, { method: 'HEAD' }).then(response => response.status).then(statusCode => {
|
||||
if (statusCode !== 200) return extension.tabs.update(tab.id, { url: '404.html' })
|
||||
extension.tabs.update(tab.id, { url: url })
|
||||
})
|
||||
.catch(err => {
|
||||
url = 'https://ipfs.infura.io/ipfs/' + ipfsHash
|
||||
extension.tabs.update(tab.id, {url: url})
|
||||
return err
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
clearTimeout(clearTime)
|
||||
const url = err === 'unsupport' ? 'unsupport' : 'error'
|
||||
extension.tabs.update(tab.id, {url: `${url}.html?name=${name}`})
|
||||
})
|
||||
})
|
||||
return { cancel: true }
|
||||
}
|
||||
|
||||
extension.webRequest.onErrorOccurred.addListener(ipfsContent, {urls: ['*://*.eth/'], types: ['main_frame']})
|
||||
|
||||
return {
|
||||
remove () {
|
||||
extension.webRequest.onErrorOccurred.removeListener(ipfsContent)
|
||||
},
|
||||
}
|
||||
}
|
@ -7,10 +7,10 @@ module.exports = reportFailedTxToSentry
|
||||
// for sending to sentry
|
||||
//
|
||||
|
||||
function reportFailedTxToSentry ({ raven, txMeta }) {
|
||||
function reportFailedTxToSentry ({ sentry, txMeta }) {
|
||||
const errorMessage = 'Transaction Failed: ' + extractEthjsErrorMessage(txMeta.err.message)
|
||||
raven.captureMessage(errorMessage, {
|
||||
sentry.captureMessage(errorMessage, {
|
||||
// "extra" key is required by Sentry
|
||||
extra: txMeta,
|
||||
extra: { txMeta },
|
||||
})
|
||||
}
|
||||
|
@ -1,71 +0,0 @@
|
||||
const namehash = require('eth-ens-namehash')
|
||||
const multihash = require('multihashes')
|
||||
const HttpProvider = require('ethjs-provider-http')
|
||||
const Eth = require('ethjs-query')
|
||||
const EthContract = require('ethjs-contract')
|
||||
const registrarAbi = require('./contracts/registrar')
|
||||
const resolverAbi = require('./contracts/resolver')
|
||||
|
||||
function ens (name, provider) {
|
||||
const eth = new Eth(new HttpProvider(getProvider(provider.type)))
|
||||
const hash = namehash.hash(name)
|
||||
const contract = new EthContract(eth)
|
||||
const Registrar = contract(registrarAbi).at(getRegistrar(provider.type))
|
||||
return new Promise((resolve, reject) => {
|
||||
if (provider.type === 'mainnet' || provider.type === 'ropsten') {
|
||||
Registrar.resolver(hash).then((address) => {
|
||||
if (address === '0x0000000000000000000000000000000000000000') {
|
||||
reject(null)
|
||||
} else {
|
||||
const Resolver = contract(resolverAbi).at(address['0'])
|
||||
return Resolver.content(hash)
|
||||
}
|
||||
}).then((contentHash) => {
|
||||
if (contentHash['0'] === '0x0000000000000000000000000000000000000000000000000000000000000000') reject(null)
|
||||
if (contentHash.ret !== '0x') {
|
||||
const hex = contentHash['0'].substring(2)
|
||||
const buf = multihash.fromHexString(hex)
|
||||
resolve(multihash.toB58String(multihash.encode(buf, 'sha2-256')))
|
||||
} else {
|
||||
reject(null)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return reject('unsupport')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getProvider (type) {
|
||||
switch (type) {
|
||||
case 'mainnet':
|
||||
return 'https://mainnet.infura.io/'
|
||||
case 'ropsten':
|
||||
return 'https://ropsten.infura.io/'
|
||||
default:
|
||||
return 'http://localhost:8545/'
|
||||
}
|
||||
}
|
||||
|
||||
function getRegistrar (type) {
|
||||
switch (type) {
|
||||
case 'mainnet':
|
||||
return '0x314159265dd8dbb310642f98f50c066173c1259b'
|
||||
case 'ropsten':
|
||||
return '0x112234455c3a32fd11230c42e7bccd4a84e02010'
|
||||
default:
|
||||
return '0x0000000000000000000000000000000000000000'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.resolve = function (name, provider) {
|
||||
const path = name.split('.')
|
||||
const topLevelDomain = path[path.length - 1]
|
||||
if (topLevelDomain === 'eth' || topLevelDomain === 'test') {
|
||||
return ens(name, provider)
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
reject(null)
|
||||
})
|
||||
}
|
||||
}
|
@ -17,9 +17,11 @@ function setupFetchDebugging() {
|
||||
try {
|
||||
return await originalFetch.call(window, ...args)
|
||||
} catch (err) {
|
||||
console.warn('FetchDebugger - fetch encountered an Error', err)
|
||||
if (!err.stack) {
|
||||
console.warn('FetchDebugger - fetch encountered an Error without a stack', err)
|
||||
console.warn('FetchDebugger - overriding stack to point of original call')
|
||||
err.stack = initialStack
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
@ -1,58 +1,55 @@
|
||||
const Raven = require('raven-js')
|
||||
const Sentry = require('@sentry/browser')
|
||||
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
||||
const extractEthjsErrorMessage = require('./extractEthjsErrorMessage')
|
||||
const PROD = 'https://3567c198f8a8412082d32655da2961d0@sentry.io/273505'
|
||||
const DEV = 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496'
|
||||
const SENTRY_DSN_PROD = 'https://3567c198f8a8412082d32655da2961d0@sentry.io/273505'
|
||||
const SENTRY_DSN_DEV = 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496'
|
||||
|
||||
module.exports = setupRaven
|
||||
module.exports = setupSentry
|
||||
|
||||
// Setup raven / sentry remote error reporting
|
||||
function setupRaven (opts) {
|
||||
const { release } = opts
|
||||
let ravenTarget
|
||||
// Setup sentry remote error reporting
|
||||
function setupSentry (opts) {
|
||||
const { release, getState } = opts
|
||||
let sentryTarget
|
||||
// detect brave
|
||||
const isBrave = Boolean(window.chrome.ipcRenderer)
|
||||
|
||||
if (METAMASK_DEBUG) {
|
||||
console.log('Setting up Sentry Remote Error Reporting: DEV')
|
||||
ravenTarget = DEV
|
||||
console.log('Setting up Sentry Remote Error Reporting: SENTRY_DSN_DEV')
|
||||
sentryTarget = SENTRY_DSN_DEV
|
||||
} else {
|
||||
console.log('Setting up Sentry Remote Error Reporting: PROD')
|
||||
ravenTarget = PROD
|
||||
console.log('Setting up Sentry Remote Error Reporting: SENTRY_DSN_PROD')
|
||||
sentryTarget = SENTRY_DSN_PROD
|
||||
}
|
||||
|
||||
const client = Raven.config(ravenTarget, {
|
||||
Sentry.init({
|
||||
dsn: sentryTarget,
|
||||
debug: METAMASK_DEBUG,
|
||||
release,
|
||||
transport: function (opts) {
|
||||
opts.data.extra.isBrave = isBrave
|
||||
const report = opts.data
|
||||
beforeSend: (report) => rewriteReport(report),
|
||||
})
|
||||
|
||||
Sentry.configureScope(scope => {
|
||||
scope.setExtra('isBrave', isBrave)
|
||||
})
|
||||
|
||||
function rewriteReport(report) {
|
||||
try {
|
||||
// handle error-like non-error exceptions
|
||||
rewriteErrorLikeExceptions(report)
|
||||
// simplify certain complex error messages (e.g. Ethjs)
|
||||
simplifyErrorMessages(report)
|
||||
// modify report urls
|
||||
rewriteReportUrls(report)
|
||||
// append app state
|
||||
if (getState) {
|
||||
const appState = getState()
|
||||
report.extra.appState = appState
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
}
|
||||
// make request normally
|
||||
client._makeRequest(opts)
|
||||
},
|
||||
})
|
||||
client.install()
|
||||
|
||||
return Raven
|
||||
return report
|
||||
}
|
||||
|
||||
function rewriteErrorLikeExceptions (report) {
|
||||
// handle errors that lost their error-ness in serialization (e.g. dnode)
|
||||
rewriteErrorMessages(report, (errorMessage) => {
|
||||
if (!errorMessage.includes('Non-Error exception captured with keys:')) return errorMessage
|
||||
if (!(report.extra && report.extra.__serialized__ && report.extra.__serialized__.message)) return errorMessage
|
||||
return `Non-Error Exception: ${report.extra.__serialized__.message}`
|
||||
})
|
||||
return Sentry
|
||||
}
|
||||
|
||||
function simplifyErrorMessages (report) {
|
@ -37,6 +37,7 @@ const TransactionController = require('./controllers/transactions')
|
||||
const BalancesController = require('./controllers/computed-balances')
|
||||
const TokenRatesController = require('./controllers/token-rates')
|
||||
const DetectTokensController = require('./controllers/detect-tokens')
|
||||
const ProviderApprovalController = require('./controllers/provider-approval')
|
||||
const nodeify = require('./lib/nodeify')
|
||||
const accountImporter = require('./account-import-strategies')
|
||||
const getBuyEthUrl = require('./lib/buy-eth-url')
|
||||
@ -89,7 +90,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.preferencesController = new PreferencesController({
|
||||
initState: initState.PreferencesController,
|
||||
initLangCode: opts.initLangCode,
|
||||
showWatchAssetUi: opts.showWatchAssetUi,
|
||||
openPopup: opts.openPopup,
|
||||
network: this.networkController,
|
||||
})
|
||||
|
||||
@ -197,6 +198,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
})
|
||||
this.networkController.on('networkDidChange', () => {
|
||||
this.balancesController.updateAllBalances()
|
||||
var currentCurrency = this.currencyController.getCurrentCurrency()
|
||||
this.setCurrentCurrency(currentCurrency, function() {})
|
||||
})
|
||||
this.balancesController.updateAllBalances()
|
||||
|
||||
@ -217,6 +220,15 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.typedMessageManager = new TypedMessageManager({ networkController: this.networkController })
|
||||
this.publicConfigStore = this.initPublicConfigStore()
|
||||
|
||||
this.providerApprovalController = new ProviderApprovalController({
|
||||
closePopup: opts.closePopup,
|
||||
keyringController: this.keyringController,
|
||||
openPopup: opts.openPopup,
|
||||
platform: opts.platform,
|
||||
preferencesController: this.preferencesController,
|
||||
publicConfigStore: this.publicConfigStore,
|
||||
})
|
||||
|
||||
this.store.updateStructure({
|
||||
TransactionController: this.txController.store,
|
||||
KeyringController: this.keyringController.store,
|
||||
@ -246,6 +258,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
NoticeController: this.noticeController.memStore,
|
||||
ShapeshiftController: this.shapeshiftController.store,
|
||||
InfuraController: this.infuraController.store,
|
||||
ProviderApprovalController: this.providerApprovalController.store,
|
||||
})
|
||||
this.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
}
|
||||
@ -261,7 +274,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
},
|
||||
version,
|
||||
// account mgmt
|
||||
getAccounts: async () => {
|
||||
getAccounts: async ({ origin }) => {
|
||||
// Expose no accounts if this origin has not been approved, preventing
|
||||
// account-requring RPC methods from completing successfully
|
||||
const exposeAccounts = this.providerApprovalController.shouldExposeAccounts(origin)
|
||||
if (origin !== 'MetaMask' && !exposeAccounts) { return [] }
|
||||
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
||||
const selectedAddress = this.preferencesController.getSelectedAddress()
|
||||
// only show address if account is unlocked
|
||||
@ -275,6 +292,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
processTransaction: this.newUnapprovedTransaction.bind(this),
|
||||
// msg signing
|
||||
processEthSignMessage: this.newUnsignedMessage.bind(this),
|
||||
processTypedMessage: this.newUnsignedTypedMessage.bind(this),
|
||||
processTypedMessageV3: this.newUnsignedTypedMessage.bind(this),
|
||||
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
|
||||
getPendingNonce: this.getPendingNonce.bind(this),
|
||||
}
|
||||
@ -345,6 +364,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
const noticeController = this.noticeController
|
||||
const addressBookController = this.addressBookController
|
||||
const networkController = this.networkController
|
||||
const providerApprovalController = this.providerApprovalController
|
||||
|
||||
return {
|
||||
// etc
|
||||
@ -402,7 +422,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
setAddressBook: nodeify(addressBookController.setAddressBook, addressBookController),
|
||||
|
||||
// KeyringController
|
||||
setLocked: nodeify(keyringController.setLocked, keyringController),
|
||||
setLocked: nodeify(this.setLocked, this),
|
||||
createNewVaultAndKeychain: nodeify(this.createNewVaultAndKeychain, this),
|
||||
createNewVaultAndRestore: nodeify(this.createNewVaultAndRestore, this),
|
||||
addNewKeyring: nodeify(keyringController.addNewKeyring, keyringController),
|
||||
@ -433,6 +453,10 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// notices
|
||||
checkNotices: noticeController.updateNoticesList.bind(noticeController),
|
||||
markNoticeRead: noticeController.markNoticeRead.bind(noticeController),
|
||||
|
||||
approveProviderRequest: providerApprovalController.approveProviderRequest.bind(providerApprovalController),
|
||||
clearApprovedOrigins: providerApprovalController.clearApprovedOrigins.bind(providerApprovalController),
|
||||
rejectProviderRequest: providerApprovalController.rejectProviderRequest.bind(providerApprovalController),
|
||||
}
|
||||
}
|
||||
|
||||
@ -978,8 +1002,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
* @param {Object} msgParams - The params passed to eth_signTypedData.
|
||||
* @param {Function} cb - The callback function, called with the signature.
|
||||
*/
|
||||
newUnsignedTypedMessage (msgParams, req) {
|
||||
const promise = this.typedMessageManager.addUnapprovedMessageAsync(msgParams, req)
|
||||
newUnsignedTypedMessage (msgParams, req, version) {
|
||||
const promise = this.typedMessageManager.addUnapprovedMessageAsync(msgParams, req, version)
|
||||
this.sendUpdate()
|
||||
this.opts.showUnconfirmedMessage()
|
||||
return promise
|
||||
@ -1273,10 +1297,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
engine.push(subscriptionManager.middleware)
|
||||
// watch asset
|
||||
engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController))
|
||||
// sign typed data middleware
|
||||
engine.push(this.createTypedDataMiddleware('eth_signTypedData', 'V1').bind(this))
|
||||
engine.push(this.createTypedDataMiddleware('eth_signTypedData_v1', 'V1').bind(this))
|
||||
engine.push(this.createTypedDataMiddleware('eth_signTypedData_v3', 'V3', true).bind(this))
|
||||
// forward to metamask primary provider
|
||||
engine.push(createProviderMiddleware({ provider }))
|
||||
|
||||
@ -1412,10 +1432,13 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
* @param {Function} cb - A callback function returning currency info.
|
||||
*/
|
||||
setCurrentCurrency (currencyCode, cb) {
|
||||
const { ticker } = this.networkController.getNetworkConfig()
|
||||
try {
|
||||
this.currencyController.setNativeCurrency(ticker)
|
||||
this.currencyController.setCurrentCurrency(currencyCode)
|
||||
this.currencyController.updateConversionRate()
|
||||
const data = {
|
||||
nativeCurrency: ticker || 'ETH',
|
||||
conversionRate: this.currencyController.getConversionRate(),
|
||||
currentCurrency: this.currencyController.getCurrentCurrency(),
|
||||
conversionDate: this.currencyController.getConversionDate(),
|
||||
@ -1454,11 +1477,14 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
/**
|
||||
* A method for selecting a custom URL for an ethereum RPC provider.
|
||||
* @param {string} rpcTarget - A URL for a valid Ethereum RPC API.
|
||||
* @param {number} chainId - The chainId of the selected network.
|
||||
* @param {string} ticker - The ticker symbol of the selected network.
|
||||
* @param {string} nickname - Optional nickname of the selected network.
|
||||
* @returns {Promise<String>} - The RPC Target URL confirmed.
|
||||
*/
|
||||
async setCustomRpc (rpcTarget) {
|
||||
this.networkController.setRpcTarget(rpcTarget)
|
||||
await this.preferencesController.addToFrequentRpcList(rpcTarget)
|
||||
async setCustomRpc (rpcTarget, chainId, ticker = 'ETH', nickname = '') {
|
||||
this.networkController.setRpcTarget(rpcTarget, chainId, ticker, nickname)
|
||||
await this.preferencesController.addToFrequentRpcList(rpcTarget, chainId, ticker, nickname)
|
||||
return rpcTarget
|
||||
}
|
||||
|
||||
@ -1542,27 +1568,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
* @param {Function} - next
|
||||
* @param {Function} - end
|
||||
*/
|
||||
createTypedDataMiddleware (methodName, version, reverse) {
|
||||
return async (req, res, next, end) => {
|
||||
const { method, params } = req
|
||||
if (method === methodName) {
|
||||
const promise = this.typedMessageManager.addUnapprovedMessageAsync({
|
||||
data: reverse ? params[1] : params[0],
|
||||
from: reverse ? params[0] : params[1],
|
||||
}, req, version)
|
||||
this.sendUpdate()
|
||||
this.opts.showUnconfirmedMessage()
|
||||
try {
|
||||
res.result = await promise
|
||||
end()
|
||||
} catch (error) {
|
||||
end(error)
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a domain to the {@link BlacklistController} whitelist
|
||||
@ -1571,4 +1576,12 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
whitelistPhishingDomain (hostname) {
|
||||
return this.blacklistController.whitelistDomain(hostname)
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks MetaMask
|
||||
*/
|
||||
setLocked() {
|
||||
this.providerApprovalController.setLocked()
|
||||
return this.keyringController.setLocked()
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,18 @@ class ExtensionPlatform {
|
||||
}
|
||||
}
|
||||
|
||||
addMessageListener (cb) {
|
||||
extension.runtime.onMessage.addListener(cb)
|
||||
}
|
||||
|
||||
sendMessage (message, query = {}) {
|
||||
extension.tabs.query(query, tabs => {
|
||||
tabs.forEach(tab => {
|
||||
extension.tabs.sendMessage(tab.id, message)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
_showConfirmedTransaction (txMeta) {
|
||||
|
||||
this._subscribeToNotificationClicked()
|
||||
|
@ -9,7 +9,7 @@ const extension = require('extensionizer')
|
||||
const ExtensionPlatform = require('./platforms/extension')
|
||||
const NotificationManager = require('./lib/notification-manager')
|
||||
const notificationManager = new NotificationManager()
|
||||
const setupRaven = require('./lib/setupRaven')
|
||||
const setupSentry = require('./lib/setupSentry')
|
||||
const log = require('loglevel')
|
||||
|
||||
start().catch(log.error)
|
||||
@ -21,7 +21,17 @@ async function start () {
|
||||
|
||||
// setup sentry error reporting
|
||||
const release = global.platform.getVersion()
|
||||
setupRaven({ release })
|
||||
setupSentry({ release, getState })
|
||||
// provide app state to append to error logs
|
||||
function getState() {
|
||||
// get app state
|
||||
const state = window.getCleanAppState()
|
||||
// remove unnecessary data
|
||||
delete state.localeMessages
|
||||
delete state.metamask.recentBlocks
|
||||
// return state to be added to request
|
||||
return state
|
||||
}
|
||||
|
||||
// inject css
|
||||
// const css = MetaMaskUiCss()
|
||||
|
@ -109,7 +109,7 @@
|
||||
},
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useETHAsPrimaryCurrency": true
|
||||
"useNativeCurrencyAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
|
@ -152,7 +152,7 @@
|
||||
},
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useETHAsPrimaryCurrency": true
|
||||
"useNativeCurrencyAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
|
@ -110,7 +110,7 @@
|
||||
},
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useETHAsPrimaryCurrency": true
|
||||
"useNativeCurrencyAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
|
@ -39,7 +39,7 @@
|
||||
"tokens": [],
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useETHAsPrimaryCurrency": true
|
||||
"useNativeCurrencyAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
|
@ -111,7 +111,7 @@
|
||||
},
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useETHAsPrimaryCurrency": true
|
||||
"useNativeCurrencyAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
|
@ -104,7 +104,7 @@
|
||||
"send": {},
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useETHAsPrimaryCurrency": true
|
||||
"useNativeCurrencyAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
|
@ -30,7 +30,7 @@ if (specifiedLocale) {
|
||||
}
|
||||
|
||||
|
||||
function verifyLocale (localeMeta) {
|
||||
function verifyLocale ({ localeMeta }) {
|
||||
const localeCode = localeMeta.code
|
||||
const localeName = localeMeta.name
|
||||
let targetLocale, englishLocale
|
||||
|
@ -33,6 +33,7 @@ const BuyView = require('./components/buy-button-subview')
|
||||
const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
|
||||
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
|
||||
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
|
||||
const ProviderApproval = require('./provider-approval')
|
||||
|
||||
module.exports = connect(mapStateToProps)(App)
|
||||
|
||||
@ -49,6 +50,7 @@ function mapStateToProps (state) {
|
||||
noActiveNotices,
|
||||
seedWords,
|
||||
featureFlags,
|
||||
providerRequests,
|
||||
} = state.metamask
|
||||
const selected = address || Object.keys(accounts)[0]
|
||||
|
||||
@ -73,8 +75,9 @@ function mapStateToProps (state) {
|
||||
forgottenPassword: state.appState.forgottenPassword,
|
||||
nextUnreadNotice: state.metamask.nextUnreadNotice,
|
||||
lostAccounts: state.metamask.lostAccounts,
|
||||
frequentRpcList: state.metamask.frequentRpcList || [],
|
||||
frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
|
||||
featureFlags,
|
||||
providerRequests,
|
||||
suggestedTokens: state.metamask.suggestedTokens,
|
||||
|
||||
// state needed to get account dropdown temporarily rendering from app bar
|
||||
@ -147,7 +150,7 @@ App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork,
|
||||
App.prototype.renderPrimary = function () {
|
||||
log.debug('rendering primary')
|
||||
var props = this.props
|
||||
const {isMascara, isOnboarding} = props
|
||||
const {isMascara, isOnboarding, providerRequests} = props
|
||||
|
||||
if (isMascara && isOnboarding) {
|
||||
return h(MascaraFirstTime)
|
||||
@ -215,6 +218,11 @@ App.prototype.renderPrimary = function () {
|
||||
return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
|
||||
}
|
||||
|
||||
if (providerRequests && providerRequests.length > 0) {
|
||||
log.debug('rendering provider API approval screen')
|
||||
return h(ProviderApproval, { origin: providerRequests[0].origin })
|
||||
}
|
||||
|
||||
// show current view
|
||||
switch (props.currentView.name) {
|
||||
|
||||
|
@ -17,7 +17,7 @@ module.exports = class AppBar extends Component {
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
frequentRpcList: PropTypes.array.isRequired,
|
||||
frequentRpcListDetail: PropTypes.array.isRequired,
|
||||
isMascara: PropTypes.bool.isRequired,
|
||||
isOnboarding: PropTypes.bool.isRequired,
|
||||
identities: PropTypes.any.isRequired,
|
||||
@ -196,7 +196,7 @@ module.exports = class AppBar extends Component {
|
||||
renderNetworkDropdown () {
|
||||
const {
|
||||
dispatch,
|
||||
frequentRpcList: rpcList,
|
||||
frequentRpcListDetail: rpcList,
|
||||
provider,
|
||||
} = this.props
|
||||
const {
|
||||
@ -321,8 +321,8 @@ module.exports = class AppBar extends Component {
|
||||
])
|
||||
}
|
||||
|
||||
renderCustomOption ({ rpcTarget, type }) {
|
||||
const {dispatch} = this.props
|
||||
renderCustomOption ({ rpcTarget, type, ticker }) {
|
||||
const {dispatch, network} = this.props
|
||||
|
||||
if (type !== 'rpc') {
|
||||
return null
|
||||
@ -340,7 +340,7 @@ module.exports = class AppBar extends Component {
|
||||
default:
|
||||
return h(DropdownMenuItem, {
|
||||
key: rpcTarget,
|
||||
onClick: () => dispatch(actions.setRpcTarget(rpcTarget)),
|
||||
onClick: () => dispatch(actions.setRpcTarget(rpcTarget, network, ticker)),
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
}, [
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
@ -354,7 +354,8 @@ module.exports = class AppBar extends Component {
|
||||
const {dispatch} = this.props
|
||||
const reversedRpcList = rpcList.slice().reverse()
|
||||
|
||||
return reversedRpcList.map((rpc) => {
|
||||
return reversedRpcList.map((entry) => {
|
||||
const rpc = entry.rpcUrl
|
||||
const currentRpcTarget = provider.type === 'rpc' && rpc === provider.rpcTarget
|
||||
|
||||
if ((rpc === LOCALHOST_RPC_URL) || currentRpcTarget) {
|
||||
@ -363,7 +364,7 @@ module.exports = class AppBar extends Component {
|
||||
return h(DropdownMenuItem, {
|
||||
key: `common${rpc}`,
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
onClick: () => dispatch(actions.setRpcTarget(rpc)),
|
||||
onClick: () => dispatch(actions.setRpcTarget(rpc, entry.chainId, entry.ticker)),
|
||||
}, [
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
rpc,
|
||||
|
@ -1,12 +1,18 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const inherits = require('util').inherits
|
||||
const formatBalance = require('../util').formatBalance
|
||||
const generateBalanceObject = require('../util').generateBalanceObject
|
||||
const Tooltip = require('./tooltip.js')
|
||||
const FiatValue = require('./fiat-value.js')
|
||||
|
||||
module.exports = EthBalanceComponent
|
||||
module.exports = connect(mapStateToProps)(EthBalanceComponent)
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
ticker: state.metamask.ticker,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(EthBalanceComponent, Component)
|
||||
function EthBalanceComponent () {
|
||||
@ -16,9 +22,10 @@ function EthBalanceComponent () {
|
||||
EthBalanceComponent.prototype.render = function () {
|
||||
var props = this.props
|
||||
let { value } = props
|
||||
const { ticker } = props
|
||||
var style = props.style
|
||||
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true
|
||||
value = value ? formatBalance(value, 6, needsParse) : '...'
|
||||
value = value ? formatBalance(value, 6, needsParse, ticker) : '...'
|
||||
var width = props.width
|
||||
|
||||
return (
|
||||
|
@ -1,12 +1,18 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const inherits = require('util').inherits
|
||||
const formatBalance = require('../util').formatBalance
|
||||
const generateBalanceObject = require('../util').generateBalanceObject
|
||||
const Tooltip = require('./tooltip.js')
|
||||
const FiatValue = require('./fiat-value.js')
|
||||
|
||||
module.exports = EthBalanceComponent
|
||||
module.exports = connect(mapStateToProps)(EthBalanceComponent)
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
ticker: state.metamask.ticker,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(EthBalanceComponent, Component)
|
||||
function EthBalanceComponent () {
|
||||
@ -16,9 +22,9 @@ function EthBalanceComponent () {
|
||||
EthBalanceComponent.prototype.render = function () {
|
||||
var props = this.props
|
||||
let { value } = props
|
||||
const { style, width } = props
|
||||
const { ticker, style, width } = props
|
||||
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true
|
||||
value = value ? formatBalance(value, 6, needsParse) : '...'
|
||||
value = value ? formatBalance(value, 6, needsParse, ticker) : '...'
|
||||
|
||||
return (
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
const Component = require('react').Component
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const actions = require('../../../ui/app/actions')
|
||||
@ -19,7 +20,9 @@ const BNInput = require('./bn-as-decimal-input')
|
||||
const MIN_GAS_PRICE_BN = new BN('0')
|
||||
const MIN_GAS_LIMIT_BN = new BN('21000')
|
||||
|
||||
module.exports = PendingTx
|
||||
module.exports = connect()(PendingTx)
|
||||
|
||||
|
||||
inherits(PendingTx, Component)
|
||||
function PendingTx () {
|
||||
Component.call(this)
|
||||
@ -445,7 +448,8 @@ PendingTx.prototype.onSubmit = function (event) {
|
||||
const txMeta = this.gatherTxMeta()
|
||||
const valid = this.checkValidity()
|
||||
this.setState({ valid, submitting: true })
|
||||
if (valid && this.verifyGasParams()) {
|
||||
const validGasParams = this.verifyGasParams()
|
||||
if (valid && validGasParams) {
|
||||
this.props.sendTransaction(txMeta, event)
|
||||
} else {
|
||||
this.props.dispatch(actions.displayWarning('Invalid Gas Parameters'))
|
||||
@ -488,8 +492,12 @@ PendingTx.prototype.verifyGasParams = function () {
|
||||
)
|
||||
}
|
||||
|
||||
PendingTx.prototype._notZeroOrEmptyString = function (obj) {
|
||||
return obj !== '' && obj !== '0x0'
|
||||
PendingTx.prototype._notZeroOrEmptyString = function (value) {
|
||||
// allow undefined values
|
||||
if (value === undefined) return true
|
||||
// Geth will return '0x', and ganache-core v2.2.1 will return '0x0'
|
||||
const valueIsEmpty = !value || value === '0x' || value === '0x0'
|
||||
return !valueIsEmpty
|
||||
}
|
||||
|
||||
PendingTx.prototype.bnMultiplyByFraction = function (targetBN, numerator, denominator) {
|
||||
|
@ -68,7 +68,7 @@ ConfigScreen.prototype.render = function () {
|
||||
|
||||
currentProviderDisplay(metamaskState),
|
||||
|
||||
h('div', { style: {display: 'flex'} }, [
|
||||
h('div', { style: {display: 'block'} }, [
|
||||
h('input#new_rpc', {
|
||||
placeholder: 'New RPC URL',
|
||||
style: {
|
||||
@ -81,7 +81,70 @@ ConfigScreen.prototype.render = function () {
|
||||
if (event.key === 'Enter') {
|
||||
var element = event.target
|
||||
var newRpc = element.value
|
||||
rpcValidation(newRpc, state)
|
||||
var chainid = document.querySelector('input#chainid')
|
||||
var ticker = document.querySelector('input#ticker')
|
||||
var nickname = document.querySelector('input#nickname')
|
||||
rpcValidation(newRpc, chainid.value, ticker.value, nickname.value, state)
|
||||
}
|
||||
},
|
||||
}),
|
||||
h('br'),
|
||||
h('input#chainid', {
|
||||
placeholder: 'ChainId (optional)',
|
||||
style: {
|
||||
width: 'inherit',
|
||||
flex: '1 0 auto',
|
||||
height: '30px',
|
||||
margin: '8px',
|
||||
},
|
||||
onKeyPress (event) {
|
||||
if (event.key === 'Enter') {
|
||||
var element = document.querySelector('input#new_rpc')
|
||||
var newRpc = element.value
|
||||
var chainid = document.querySelector('input#chainid')
|
||||
var ticker = document.querySelector('input#ticker')
|
||||
var nickname = document.querySelector('input#nickname')
|
||||
rpcValidation(newRpc, chainid.value, ticker.value, nickname.value, state)
|
||||
}
|
||||
},
|
||||
}),
|
||||
h('br'),
|
||||
h('input#ticker', {
|
||||
placeholder: 'Symbol (optional)',
|
||||
style: {
|
||||
width: 'inherit',
|
||||
flex: '1 0 auto',
|
||||
height: '30px',
|
||||
margin: '8px',
|
||||
},
|
||||
onKeyPress (event) {
|
||||
if (event.key === 'Enter') {
|
||||
var element = document.querySelector('input#new_rpc')
|
||||
var newRpc = element.value
|
||||
var chainid = document.querySelector('input#chainid')
|
||||
var ticker = document.querySelector('input#ticker')
|
||||
var nickname = document.querySelector('input#nickname')
|
||||
rpcValidation(newRpc, chainid.value, ticker.value, nickname.value, state)
|
||||
}
|
||||
},
|
||||
}),
|
||||
h('br'),
|
||||
h('input#nickname', {
|
||||
placeholder: 'Nickname (optional)',
|
||||
style: {
|
||||
width: 'inherit',
|
||||
flex: '1 0 auto',
|
||||
height: '30px',
|
||||
margin: '8px',
|
||||
},
|
||||
onKeyPress (event) {
|
||||
if (event.key === 'Enter') {
|
||||
var element = document.querySelector('input#new_rpc')
|
||||
var newRpc = element.value
|
||||
var chainid = document.querySelector('input#chainid')
|
||||
var ticker = document.querySelector('input#ticker')
|
||||
var nickname = document.querySelector('input#nickname')
|
||||
rpcValidation(newRpc, chainid.value, ticker.value, nickname.value, state)
|
||||
}
|
||||
},
|
||||
}),
|
||||
@ -93,7 +156,10 @@ ConfigScreen.prototype.render = function () {
|
||||
event.preventDefault()
|
||||
var element = document.querySelector('input#new_rpc')
|
||||
var newRpc = element.value
|
||||
rpcValidation(newRpc, state)
|
||||
var chainid = document.querySelector('input#chainid')
|
||||
var ticker = document.querySelector('input#ticker')
|
||||
var nickname = document.querySelector('input#nickname')
|
||||
rpcValidation(newRpc, chainid.value, ticker.value, nickname.value, state)
|
||||
},
|
||||
}, 'Save'),
|
||||
]),
|
||||
@ -134,6 +200,62 @@ ConfigScreen.prototype.render = function () {
|
||||
|
||||
h('hr.horizontal-line'),
|
||||
|
||||
h('div', {
|
||||
style: {
|
||||
marginTop: '20px',
|
||||
},
|
||||
}, [
|
||||
h('p', {
|
||||
style: {
|
||||
fontFamily: 'Montserrat Light',
|
||||
fontSize: '13px',
|
||||
},
|
||||
}, 'Clear privacy data so all websites must request access to view account information again.'),
|
||||
h('br'),
|
||||
h('button', {
|
||||
style: {
|
||||
alignSelf: 'center',
|
||||
},
|
||||
onClick (event) {
|
||||
event.preventDefault()
|
||||
state.dispatch(actions.clearApprovedOrigins())
|
||||
},
|
||||
}, 'Clear privacy data'),
|
||||
]),
|
||||
|
||||
h('hr.horizontal-line'),
|
||||
|
||||
h('div', {
|
||||
style: {
|
||||
marginTop: '20px',
|
||||
},
|
||||
}, [
|
||||
h('p', {
|
||||
style: {
|
||||
fontFamily: 'Montserrat Light',
|
||||
fontSize: '13px',
|
||||
},
|
||||
}, metamaskState.featureFlags.privacyMode ?
|
||||
'Websites will be able to view your account information.' :
|
||||
'Websites must request access to view your account information.'
|
||||
),
|
||||
h('br'),
|
||||
h('button', {
|
||||
style: {
|
||||
alignSelf: 'center',
|
||||
},
|
||||
onClick (event) {
|
||||
event.preventDefault()
|
||||
state.dispatch(actions.setFeatureFlag('privacyMode', !metamaskState.featureFlags.privacyMode))
|
||||
},
|
||||
}, metamaskState.featureFlags.privacyMode ?
|
||||
'Disable privacy mode' :
|
||||
'Enable privacy mode'
|
||||
),
|
||||
]),
|
||||
|
||||
h('hr.horizontal-line'),
|
||||
|
||||
h('div', {
|
||||
style: {
|
||||
marginTop: '20px',
|
||||
@ -189,9 +311,9 @@ ConfigScreen.prototype.render = function () {
|
||||
)
|
||||
}
|
||||
|
||||
function rpcValidation (newRpc, state) {
|
||||
function rpcValidation (newRpc, chainid, ticker = 'ETH', nickname = '', state) {
|
||||
if (validUrl.isWebUri(newRpc)) {
|
||||
state.dispatch(actions.setRpcTarget(newRpc))
|
||||
state.dispatch(actions.setRpcTarget(newRpc, chainid, ticker, nickname))
|
||||
} else {
|
||||
var appendedRpc = `http://${newRpc}`
|
||||
if (validUrl.isWebUri(appendedRpc)) {
|
||||
|
64
old-ui/app/provider-approval.js
Normal file
64
old-ui/app/provider-approval.js
Normal file
@ -0,0 +1,64 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React, { Component } from 'react'
|
||||
import { approveProviderRequest, rejectProviderRequest } from '../../ui/app/actions'
|
||||
import { connect } from 'react-redux'
|
||||
class ProviderApproval extends Component {
|
||||
render () {
|
||||
const { approveProviderRequest, origin, rejectProviderRequest } = this.props
|
||||
return (
|
||||
<div className="flex-column flex-grow">
|
||||
<style dangerouslySetInnerHTML={{__html: `
|
||||
.provider_approval_actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin: 14px 25px;
|
||||
}
|
||||
.provider_approval_actions button {
|
||||
margin-left: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.provider_approval_content {
|
||||
padding: 0 25px;
|
||||
}
|
||||
.provider_approval_origin {
|
||||
font-weight: bold;
|
||||
margin: 14px 0;
|
||||
}
|
||||
`}} />
|
||||
<div className="section-title flex-row flex-center">
|
||||
<i
|
||||
className="fa fa-arrow-left fa-lg cursor-pointer"
|
||||
onClick={() => { rejectProviderRequest(origin) }} />
|
||||
<h2 className="page-subtitle">Web3 API Request</h2>
|
||||
</div>
|
||||
<div className="provider_approval_content">
|
||||
{"The domain listed below is requesting access to the Ethereum blockchain and to view your current account. Always double check that you're on the correct site before approving access."}
|
||||
<div className="provider_approval_origin">{origin}</div>
|
||||
</div>
|
||||
<div className="provider_approval_actions">
|
||||
<button
|
||||
className="btn-green"
|
||||
onClick={() => { approveProviderRequest(origin) }}>APPROVE</button>
|
||||
<button
|
||||
className="cancel btn-red"
|
||||
onClick={() => { rejectProviderRequest(origin) }}>REJECT</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ProviderApproval.propTypes = {
|
||||
approveProviderRequest: PropTypes.func,
|
||||
origin: PropTypes.string,
|
||||
rejectProviderRequest: PropTypes.func,
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
approveProviderRequest: origin => dispatch(approveProviderRequest(origin)),
|
||||
rejectProviderRequest: origin => dispatch(rejectProviderRequest(origin)),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(null, mapDispatchToProps)(ProviderApproval)
|
@ -102,7 +102,7 @@ function parseBalance (balance) {
|
||||
|
||||
// Takes wei hex, returns an object with three properties.
|
||||
// Its "formatted" property is what we generally use to render values.
|
||||
function formatBalance (balance, decimalsToKeep, needsParse = true) {
|
||||
function formatBalance (balance, decimalsToKeep, needsParse = true, ticker = 'ETH') {
|
||||
var parsed = needsParse ? parseBalance(balance) : balance.split('.')
|
||||
var beforeDecimal = parsed[0]
|
||||
var afterDecimal = parsed[1]
|
||||
@ -112,14 +112,14 @@ function formatBalance (balance, decimalsToKeep, needsParse = true) {
|
||||
if (afterDecimal !== '0') {
|
||||
var sigFigs = afterDecimal.match(/^0*(.{2})/) // default: grabs 2 most significant digits
|
||||
if (sigFigs) { afterDecimal = sigFigs[0] }
|
||||
formatted = '0.' + afterDecimal + ' ETH'
|
||||
formatted = '0.' + afterDecimal + ` ${ticker}`
|
||||
}
|
||||
} else {
|
||||
formatted = beforeDecimal + '.' + afterDecimal.slice(0, 3) + ' ETH'
|
||||
formatted = beforeDecimal + '.' + afterDecimal.slice(0, 3) + ` ${ticker}`
|
||||
}
|
||||
} else {
|
||||
afterDecimal += Array(decimalsToKeep).join('0')
|
||||
formatted = beforeDecimal + '.' + afterDecimal.slice(0, decimalsToKeep) + ' ETH'
|
||||
formatted = beforeDecimal + '.' + afterDecimal.slice(0, decimalsToKeep) + ` ${ticker}`
|
||||
}
|
||||
return formatted
|
||||
}
|
||||
|
2186
package-lock.json
generated
2186
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -82,6 +82,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "1.0.0",
|
||||
"@sentry/browser": "^4.1.1",
|
||||
"@zxing/library": "^0.8.0",
|
||||
"abi-decoder": "^1.0.9",
|
||||
"asmcrypto.js": "0.22.0",
|
||||
@ -97,7 +98,7 @@
|
||||
"browserify-derequire": "^0.9.4",
|
||||
"browserify-unibabel": "^3.0.0",
|
||||
"classnames": "^2.2.5",
|
||||
"clone": "^2.1.1",
|
||||
"clone": "^2.1.2",
|
||||
"copy-to-clipboard": "^3.0.8",
|
||||
"css-loader": "^0.28.11",
|
||||
"currency-formatter": "^1.4.2",
|
||||
@ -112,7 +113,7 @@
|
||||
"ensnare": "^1.0.0",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"eth-bin-to-ops": "^1.0.1",
|
||||
"eth-block-tracker": "^4.0.3",
|
||||
"eth-block-tracker": "^4.1.0",
|
||||
"eth-contract-metadata": "github:MetaMask/eth-contract-metadata#master",
|
||||
"eth-ens-namehash": "^2.0.8",
|
||||
"eth-hd-keyring": "^1.2.2",
|
||||
@ -186,7 +187,6 @@
|
||||
"pumpify": "^1.3.4",
|
||||
"qrcode-npm": "0.0.3",
|
||||
"ramda": "^0.24.1",
|
||||
"raven-js": "^3.24.2",
|
||||
"react": "^15.6.2",
|
||||
"react-addons-css-transition-group": "^15.6.0",
|
||||
"react-dom": "^15.6.2",
|
||||
@ -261,7 +261,7 @@
|
||||
"eslint-plugin-json": "^1.2.0",
|
||||
"eslint-plugin-mocha": "^5.0.0",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"eth-json-rpc-middleware": "^3.1.3",
|
||||
"eth-json-rpc-middleware": "^3.1.6",
|
||||
"eth-keyring-controller": "^3.3.1",
|
||||
"fetch-mock": "^6.5.2",
|
||||
"file-loader": "^1.1.11",
|
||||
|
@ -111,7 +111,9 @@
|
||||
"0x108cf70c7d384c552f42c07c41c0e1e46d77ea0d": 0.00039345803819379796,
|
||||
"0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5": 0.00008189274407698049
|
||||
},
|
||||
"ticker": "ETH",
|
||||
"currentCurrency": "usd",
|
||||
"nativeCurrency": "ETH",
|
||||
"conversionRate": 556.12,
|
||||
"addressBook": [
|
||||
{
|
||||
|
@ -28,6 +28,7 @@ The `piggybankContract` is compiled from:
|
||||
}
|
||||
*/
|
||||
|
||||
web3.currentProvider.enable().then(() => {
|
||||
var piggybankContract = web3.eth.contract([{'constant': false, 'inputs': [{'name': 'withdrawAmount', 'type': 'uint256'}], 'name': 'withdraw', 'outputs': [{'name': 'remainingBal', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'owner', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [], 'name': 'deposit', 'outputs': [{'name': '', 'type': 'uint256'}], 'payable': true, 'stateMutability': 'payable', 'type': 'function'}, {'inputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}])
|
||||
const deployButton = document.getElementById('deployButton')
|
||||
const depositButton = document.getElementById('depositButton')
|
||||
@ -138,4 +139,4 @@ createToken.addEventListener('click', async function (event) {
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
@ -19,6 +19,7 @@ const {
|
||||
openNewPage,
|
||||
verboseReportOnFailure,
|
||||
waitUntilXWindowHandles,
|
||||
switchToWindowWithTitle,
|
||||
} = require('./helpers')
|
||||
|
||||
describe('MetaMask', function () {
|
||||
@ -266,18 +267,32 @@ describe('MetaMask', function () {
|
||||
})
|
||||
|
||||
describe('Drizzle', () => {
|
||||
it('should be able to detect our eth address', async () => {
|
||||
let windowHandles
|
||||
let extension
|
||||
let popup
|
||||
let dapp
|
||||
|
||||
it('should be able to connect the account', async () => {
|
||||
await openNewPage(driver, 'http://127.0.0.1:3000/')
|
||||
await delay(regularDelayMs)
|
||||
|
||||
await waitUntilXWindowHandles(driver, 2)
|
||||
const windowHandles = await driver.getAllWindowHandles()
|
||||
const dapp = windowHandles[1]
|
||||
await waitUntilXWindowHandles(driver, 3)
|
||||
windowHandles = await driver.getAllWindowHandles()
|
||||
|
||||
extension = windowHandles[0]
|
||||
popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles)
|
||||
dapp = windowHandles.find(handle => handle !== extension && handle !== popup)
|
||||
|
||||
await delay(regularDelayMs)
|
||||
const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`))
|
||||
await approveButton.click()
|
||||
})
|
||||
|
||||
it('should be able to detect our eth address', async () => {
|
||||
// Check if address exposed
|
||||
await driver.switchTo().window(dapp)
|
||||
await delay(regularDelayMs)
|
||||
|
||||
|
||||
const addressElement = await findElement(driver, By.css(`.pure-u-1-1 h4`))
|
||||
const addressText = await addressElement.getText()
|
||||
assert(addressText.match(/^0x[a-fA-F0-9]{40}$/))
|
||||
|
@ -319,7 +319,7 @@ describe('Using MetaMask with an existing account', function () {
|
||||
|
||||
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
assert.equal(txValues.length, 1)
|
||||
assert.equal(await txValues[0].getText(), '-1 ETH')
|
||||
assert.ok(/-1\s*ETH/.test(await txValues[0].getText()))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -14,6 +14,7 @@ module.exports = {
|
||||
loadExtension,
|
||||
openNewPage,
|
||||
switchToWindowWithTitle,
|
||||
switchToWindowWithUrlThatMatches,
|
||||
verboseReportOnFailure,
|
||||
waitUntilXWindowHandles,
|
||||
}
|
||||
@ -130,3 +131,19 @@ async function assertElementNotPresent (webdriver, driver, by) {
|
||||
}
|
||||
assert.ok(!dataTab, 'Found element that should not be present')
|
||||
}
|
||||
|
||||
async function switchToWindowWithUrlThatMatches (driver, regexp, windowHandles) {
|
||||
if (!windowHandles) {
|
||||
windowHandles = await driver.getAllWindowHandles()
|
||||
} else if (windowHandles.length === 0) {
|
||||
throw new Error('No window that matches: ' + regexp)
|
||||
}
|
||||
const firstHandle = windowHandles[0]
|
||||
await driver.switchTo().window(firstHandle)
|
||||
const windowUrl = await driver.getCurrentUrl()
|
||||
if (windowUrl.match(regexp)) {
|
||||
return firstHandle
|
||||
} else {
|
||||
return await switchToWindowWithUrlThatMatches(driver, regexp, windowHandles.slice(1))
|
||||
}
|
||||
}
|
||||
|
@ -284,6 +284,22 @@ describe('MetaMask', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('Enable privacy mode', () => {
|
||||
it('enables privacy mode', async () => {
|
||||
const networkDropdown = await findElement(driver, By.css('.network-name'))
|
||||
await networkDropdown.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const customRpcButton = await findElement(driver, By.xpath(`//span[contains(text(), 'Custom RPC')]`))
|
||||
await customRpcButton.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const privacyToggle = await findElement(driver, By.css('.settings-page__content-row:nth-of-type(10) .settings-page__content-item-col > div'))
|
||||
await privacyToggle.click()
|
||||
await delay(largeDelayMs * 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Log out an log back in', () => {
|
||||
it('logs out of the account', async () => {
|
||||
await driver.findElement(By.css('.account-menu__icon')).click()
|
||||
@ -371,7 +387,7 @@ describe('MetaMask', function () {
|
||||
|
||||
it('balance renders', async () => {
|
||||
const balance = await findElement(driver, By.css('.balance-display .token-amount'))
|
||||
await driver.wait(until.elementTextMatches(balance, /100.+ETH/))
|
||||
await driver.wait(until.elementTextMatches(balance, /100\s*ETH/))
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
})
|
||||
@ -420,30 +436,43 @@ describe('MetaMask', function () {
|
||||
|
||||
if (process.env.SELENIUM_BROWSER !== 'firefox') {
|
||||
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txValues, /-1\sETH/), 10000)
|
||||
await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('Send ETH from dapp', () => {
|
||||
let windowHandles
|
||||
let extension
|
||||
let popup
|
||||
let dapp
|
||||
|
||||
it('starts a send transaction inside the dapp', async () => {
|
||||
await openNewPage(driver, 'http://127.0.0.1:8080/')
|
||||
await delay(regularDelayMs)
|
||||
|
||||
await waitUntilXWindowHandles(driver, 2)
|
||||
let windowHandles = await driver.getAllWindowHandles()
|
||||
const extension = windowHandles[0]
|
||||
const dapp = windowHandles[1]
|
||||
await waitUntilXWindowHandles(driver, 3)
|
||||
windowHandles = await driver.getAllWindowHandles()
|
||||
|
||||
extension = windowHandles[0]
|
||||
popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles)
|
||||
dapp = windowHandles.find(handle => handle !== extension && handle !== popup)
|
||||
|
||||
await delay(regularDelayMs)
|
||||
const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`))
|
||||
await approveButton.click()
|
||||
})
|
||||
|
||||
it('initiates a send from the dapp', async () => {
|
||||
await driver.switchTo().window(dapp)
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const send3eth = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`), 10000)
|
||||
await send3eth.click()
|
||||
await delay(regularDelayMs)
|
||||
await delay(5000)
|
||||
|
||||
windowHandles = await driver.getAllWindowHandles()
|
||||
await driver.switchTo().window(windowHandles[2])
|
||||
await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles)
|
||||
await delay(regularDelayMs)
|
||||
|
||||
await assertElementNotPresent(webdriver, driver, By.xpath(`//li[contains(text(), 'Data')]`))
|
||||
@ -462,7 +491,7 @@ describe('MetaMask', function () {
|
||||
assert.equal(transactions.length, 2)
|
||||
|
||||
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txValues, /-3\sETH/), 10000)
|
||||
await driver.wait(until.elementTextMatches(txValues, /-3\s*ETH/), 10000)
|
||||
})
|
||||
})
|
||||
|
||||
@ -540,7 +569,7 @@ describe('MetaMask', function () {
|
||||
|
||||
await findElements(driver, By.css('.transaction-list-item'))
|
||||
const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txListValue, /-4\sETH/), 10000)
|
||||
await driver.wait(until.elementTextMatches(txListValue, /-4\s*ETH/), 10000)
|
||||
await txListValue.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
@ -574,7 +603,7 @@ describe('MetaMask', function () {
|
||||
}, 10000)
|
||||
|
||||
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txValues[0], /-4\sETH/), 10000)
|
||||
await driver.wait(until.elementTextMatches(txValues[0], /-4\s*ETH/), 10000)
|
||||
|
||||
// const txAccounts = await findElements(driver, By.css('.tx-list-account'))
|
||||
// const firstTxAddress = await txAccounts[0].getText()
|
||||
@ -606,7 +635,7 @@ describe('MetaMask', function () {
|
||||
}, 10000)
|
||||
|
||||
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txValues, /-0\sETH/), 10000)
|
||||
await driver.wait(until.elementTextMatches(txValues, /-0\s*ETH/), 10000)
|
||||
|
||||
await closeAllWindowHandlesExcept(driver, [extension, dapp])
|
||||
await driver.switchTo().window(extension)
|
||||
@ -616,9 +645,9 @@ describe('MetaMask', function () {
|
||||
const balance = await findElement(driver, By.css('.transaction-view-balance__primary-balance'))
|
||||
await delay(regularDelayMs)
|
||||
if (process.env.SELENIUM_BROWSER !== 'firefox') {
|
||||
await driver.wait(until.elementTextMatches(balance, /^92.*ETH.*$/), 10000)
|
||||
await driver.wait(until.elementTextMatches(balance, /^92.*\s*ETH.*$/), 10000)
|
||||
const tokenAmount = await balance.getText()
|
||||
assert.ok(/^92.*ETH.*$/.test(tokenAmount))
|
||||
assert.ok(/^92.*\s*ETH.*$/.test(tokenAmount))
|
||||
await delay(regularDelayMs)
|
||||
}
|
||||
})
|
||||
@ -764,7 +793,7 @@ describe('MetaMask', function () {
|
||||
// test cancelled on firefox until https://github.com/mozilla/geckodriver/issues/906 is resolved,
|
||||
// or possibly until we use latest version of firefox in the tests
|
||||
if (process.env.SELENIUM_BROWSER !== 'firefox') {
|
||||
await driver.wait(until.elementTextMatches(txValues[0], /-50\sTST/), 10000)
|
||||
await driver.wait(until.elementTextMatches(txValues[0], /-50\s*TST/), 10000)
|
||||
}
|
||||
|
||||
driver.wait(async () => {
|
||||
@ -798,7 +827,7 @@ describe('MetaMask', function () {
|
||||
|
||||
await findElements(driver, By.css('.transaction-list__pending-transactions'))
|
||||
const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txListValue, /-7\sTST/), 10000)
|
||||
await driver.wait(until.elementTextMatches(txListValue, /-7\s*TST/), 10000)
|
||||
await txListValue.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
@ -851,7 +880,7 @@ describe('MetaMask', function () {
|
||||
}, 10000)
|
||||
|
||||
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txValues[0], /-7\sTST/))
|
||||
await driver.wait(until.elementTextMatches(txValues[0], /-7\s*TST/))
|
||||
const txStatuses = await findElements(driver, By.css('.transaction-list-item__action'))
|
||||
await driver.wait(until.elementTextMatches(txStatuses[0], /Sent\sToken/))
|
||||
|
||||
@ -897,7 +926,7 @@ describe('MetaMask', function () {
|
||||
|
||||
const [txListItem] = await findElements(driver, By.css('.transaction-list-item'))
|
||||
const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txListValue, /-7\sTST/))
|
||||
await driver.wait(until.elementTextMatches(txListValue, /-7\s*TST/))
|
||||
await txListItem.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
@ -974,7 +1003,7 @@ describe('MetaMask', function () {
|
||||
}, 10000)
|
||||
|
||||
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txValues[0], /-7\sTST/))
|
||||
await driver.wait(until.elementTextMatches(txValues[0], /-7\s*TST/))
|
||||
const txStatuses = await findElements(driver, By.css('.transaction-list-item__action'))
|
||||
await driver.wait(until.elementTextMatches(txStatuses[0], /Approve/))
|
||||
})
|
||||
@ -1027,7 +1056,7 @@ describe('MetaMask', function () {
|
||||
|
||||
it('renders the balance for the chosen token', async () => {
|
||||
const balance = await findElement(driver, By.css('.transaction-view-balance__token-balance'))
|
||||
await driver.wait(until.elementTextMatches(balance, /0\sBAT/))
|
||||
await driver.wait(until.elementTextMatches(balance, /0\s*BAT/))
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
})
|
||||
|
@ -11,7 +11,7 @@ sleep 5
|
||||
cd test/e2e/beta/
|
||||
rm -rf drizzle-test
|
||||
mkdir drizzle-test && cd drizzle-test
|
||||
npm install truffle
|
||||
sudo npm install -g truffle
|
||||
truffle unbox drizzle
|
||||
echo "Deploying contracts for Drizzle test..."
|
||||
truffle compile && truffle migrate
|
||||
|
@ -77,7 +77,7 @@ async function runFirstTimeUsageTest (assert, done) {
|
||||
assert.ok(lock, 'Lock menu item found')
|
||||
lock.click()
|
||||
|
||||
await timeout(1000)
|
||||
await timeout(5000)
|
||||
|
||||
const pwBox2 = (await findAsync(app, '#password'))[0]
|
||||
pwBox2.focus()
|
||||
|
@ -47,7 +47,7 @@ describe('# Network Controller', function () {
|
||||
|
||||
describe('#setNetworkState', function () {
|
||||
it('should update the network', function () {
|
||||
networkController.setNetworkState(1)
|
||||
networkController.setNetworkState(1, 'rpc')
|
||||
const networkState = networkController.getNetworkState()
|
||||
assert.equal(networkState, 1, 'network is 1')
|
||||
})
|
||||
|
@ -418,7 +418,7 @@ describe('preferences controller', function () {
|
||||
req.params.options = { address, symbol, decimals, image }
|
||||
|
||||
sandbox.stub(preferencesController, '_validateERC20AssetParams').returns(true)
|
||||
preferencesController.showWatchAssetUi = async () => {}
|
||||
preferencesController.openPopup = async () => {}
|
||||
|
||||
await preferencesController._handleWatchAssetERC20(req.params.options)
|
||||
const suggested = preferencesController.getSuggestedTokens()
|
||||
@ -438,7 +438,7 @@ describe('preferences controller', function () {
|
||||
req.params.options = { address, symbol, decimals, image }
|
||||
|
||||
sandbox.stub(preferencesController, '_validateERC20AssetParams').returns(true)
|
||||
preferencesController.showWatchAssetUi = async () => {
|
||||
preferencesController.openPopup = async () => {
|
||||
await preferencesController.addToken(address, symbol, decimals, image)
|
||||
}
|
||||
|
||||
@ -453,6 +453,32 @@ describe('preferences controller', function () {
|
||||
const assetImages = preferencesController.getAssetImages()
|
||||
assert.ok(assetImages[address], `set image correctly`)
|
||||
})
|
||||
it('should validate ERC20 asset correctly', async function () {
|
||||
const validateSpy = sandbox.spy(preferencesController._validateERC20AssetParams)
|
||||
try { validateSpy({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC', decimals: 0}) } catch (e) {}
|
||||
assert.equal(validateSpy.threw(), false, 'correct options object')
|
||||
const validateSpyAddress = sandbox.spy(preferencesController._validateERC20AssetParams)
|
||||
try { validateSpyAddress({symbol: 'ABC', decimals: 0}) } catch (e) {}
|
||||
assert.equal(validateSpyAddress.threw(), true, 'options object with no address')
|
||||
const validateSpySymbol = sandbox.spy(preferencesController._validateERC20AssetParams)
|
||||
try { validateSpySymbol({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', decimals: 0}) } catch (e) {}
|
||||
assert.equal(validateSpySymbol.threw(), true, 'options object with no symbol')
|
||||
const validateSpyDecimals = sandbox.spy(preferencesController._validateERC20AssetParams)
|
||||
try { validateSpyDecimals({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC'}) } catch (e) {}
|
||||
assert.equal(validateSpyDecimals.threw(), true, 'options object with no decimals')
|
||||
const validateSpyInvalidSymbol = sandbox.spy(preferencesController._validateERC20AssetParams)
|
||||
try { validateSpyInvalidSymbol({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: 0}) } catch (e) {}
|
||||
assert.equal(validateSpyInvalidSymbol.threw(), true, 'options object with invalid symbol')
|
||||
const validateSpyInvalidDecimals1 = sandbox.spy(preferencesController._validateERC20AssetParams)
|
||||
try { validateSpyInvalidDecimals1({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: -1}) } catch (e) {}
|
||||
assert.equal(validateSpyInvalidDecimals1.threw(), true, 'options object with decimals less than zero')
|
||||
const validateSpyInvalidDecimals2 = sandbox.spy(preferencesController._validateERC20AssetParams)
|
||||
try { validateSpyInvalidDecimals2({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: 38}) } catch (e) {}
|
||||
assert.equal(validateSpyInvalidDecimals2.threw(), true, 'options object with decimals more than 36')
|
||||
const validateSpyInvalidAddress = sandbox.spy(preferencesController._validateERC20AssetParams)
|
||||
try { validateSpyInvalidAddress({rawAddress: '0x123', symbol: 'ABC', decimals: 0}) } catch (e) {}
|
||||
assert.equal(validateSpyInvalidAddress.threw(), true, 'options object with address invalid')
|
||||
})
|
||||
})
|
||||
|
||||
describe('setPasswordForgotten', function () {
|
||||
@ -487,20 +513,20 @@ describe('preferences controller', function () {
|
||||
|
||||
describe('on updateFrequentRpcList', function () {
|
||||
it('should add custom RPC url to state', function () {
|
||||
preferencesController.addToFrequentRpcList('rpc_url')
|
||||
preferencesController.addToFrequentRpcList('http://localhost:8545')
|
||||
assert.deepEqual(preferencesController.store.getState().frequentRpcList, ['rpc_url'])
|
||||
preferencesController.addToFrequentRpcList('rpc_url')
|
||||
assert.deepEqual(preferencesController.store.getState().frequentRpcList, ['rpc_url'])
|
||||
preferencesController.addToFrequentRpcList('rpc_url', 1)
|
||||
preferencesController.addToFrequentRpcList('http://localhost:8545', 1)
|
||||
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '' }] )
|
||||
preferencesController.addToFrequentRpcList('rpc_url', 1)
|
||||
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '' }] )
|
||||
})
|
||||
|
||||
it('should remove custom RPC url from state', function () {
|
||||
preferencesController.addToFrequentRpcList('rpc_url')
|
||||
assert.deepEqual(preferencesController.store.getState().frequentRpcList, ['rpc_url'])
|
||||
preferencesController.addToFrequentRpcList('rpc_url', 1)
|
||||
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '' }] )
|
||||
preferencesController.removeFromFrequentRpcList('other_rpc_url')
|
||||
preferencesController.removeFromFrequentRpcList('http://localhost:8545')
|
||||
preferencesController.removeFromFrequentRpcList('rpc_url')
|
||||
assert.deepEqual(preferencesController.store.getState().frequentRpcList, [])
|
||||
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1133,7 +1133,7 @@ describe('Actions', () => {
|
||||
{ type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' },
|
||||
]
|
||||
|
||||
setRpcTargetSpy.callsFake((newRpc, callback) => {
|
||||
setRpcTargetSpy.callsFake((newRpc, chainId, ticker, nickname, callback) => {
|
||||
callback(new Error('error'))
|
||||
})
|
||||
|
||||
|
@ -309,7 +309,7 @@ var actions = {
|
||||
setPreference,
|
||||
updatePreferences,
|
||||
UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
|
||||
setUseETHAsPrimaryCurrencyPreference,
|
||||
setUseNativeCurrencyAsPrimaryCurrencyPreference,
|
||||
|
||||
setMouseUserState,
|
||||
SET_MOUSE_USER_STATE: 'SET_MOUSE_USER_STATE',
|
||||
@ -325,6 +325,9 @@ var actions = {
|
||||
clearPendingTokens,
|
||||
|
||||
createCancelTransaction,
|
||||
approveProviderRequest,
|
||||
rejectProviderRequest,
|
||||
clearApprovedOrigins,
|
||||
}
|
||||
|
||||
module.exports = actions
|
||||
@ -1874,10 +1877,10 @@ function updateProviderType (type) {
|
||||
}
|
||||
}
|
||||
|
||||
function setRpcTarget (newRpc) {
|
||||
function setRpcTarget (newRpc, chainId, ticker = 'ETH', nickname = '') {
|
||||
return (dispatch) => {
|
||||
log.debug(`background.setRpcTarget: ${newRpc}`)
|
||||
background.setCustomRpc(newRpc, (err, result) => {
|
||||
log.debug(`background.setRpcTarget: ${newRpc} ${chainId} ${ticker} ${nickname}`)
|
||||
background.setCustomRpc(newRpc, chainId, ticker, nickname, (err, result) => {
|
||||
if (err) {
|
||||
log.error(err)
|
||||
return dispatch(actions.displayWarning('Had a problem changing networks!'))
|
||||
@ -2330,8 +2333,8 @@ function updatePreferences (value) {
|
||||
}
|
||||
}
|
||||
|
||||
function setUseETHAsPrimaryCurrencyPreference (value) {
|
||||
return setPreference('useETHAsPrimaryCurrency', value)
|
||||
function setUseNativeCurrencyAsPrimaryCurrencyPreference (value) {
|
||||
return setPreference('useNativeCurrencyAsPrimaryCurrency', value)
|
||||
}
|
||||
|
||||
function setNetworkNonce (networkNonce) {
|
||||
@ -2484,3 +2487,21 @@ function setPendingTokens (pendingTokens) {
|
||||
payload: tokens,
|
||||
}
|
||||
}
|
||||
|
||||
function approveProviderRequest (origin) {
|
||||
return (dispatch) => {
|
||||
background.approveProviderRequest(origin)
|
||||
}
|
||||
}
|
||||
|
||||
function rejectProviderRequest (origin) {
|
||||
return (dispatch) => {
|
||||
background.rejectProviderRequest(origin)
|
||||
}
|
||||
}
|
||||
|
||||
function clearApprovedOrigins () {
|
||||
return (dispatch) => {
|
||||
background.clearApprovedOrigins()
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ class App extends Component {
|
||||
network,
|
||||
isMouseUser,
|
||||
provider,
|
||||
frequentRpcList,
|
||||
frequentRpcListDetail,
|
||||
currentView,
|
||||
setMouseUserState,
|
||||
sidebar,
|
||||
@ -147,7 +147,7 @@ class App extends Component {
|
||||
// network dropdown
|
||||
h(NetworkDropdown, {
|
||||
provider,
|
||||
frequentRpcList,
|
||||
frequentRpcListDetail,
|
||||
}, []),
|
||||
|
||||
h(AccountMenu),
|
||||
@ -230,7 +230,7 @@ App.propTypes = {
|
||||
alertMessage: PropTypes.string,
|
||||
network: PropTypes.string,
|
||||
provider: PropTypes.object,
|
||||
frequentRpcList: PropTypes.array,
|
||||
frequentRpcListDetail: PropTypes.array,
|
||||
currentView: PropTypes.object,
|
||||
sidebar: PropTypes.object,
|
||||
alertOpen: PropTypes.bool,
|
||||
@ -322,7 +322,7 @@ function mapStateToProps (state) {
|
||||
forgottenPassword: state.appState.forgottenPassword,
|
||||
nextUnreadNotice,
|
||||
lostAccounts,
|
||||
frequentRpcList: state.metamask.frequentRpcList || [],
|
||||
frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
|
||||
currentCurrency: state.metamask.currentCurrency,
|
||||
isMouseUser: state.appState.isMouseUser,
|
||||
betaUI: state.metamask.featureFlags.betaUI,
|
||||
|
@ -6,10 +6,11 @@ const genAccountLink = require('etherscan-link').createAccountLink
|
||||
const connect = require('react-redux').connect
|
||||
const Dropdown = require('./dropdown').Dropdown
|
||||
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
|
||||
const Identicon = require('./identicon')
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
const { checksumAddress } = require('../util')
|
||||
|
||||
import Identicon from './identicon'
|
||||
|
||||
class AccountDropdowns extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
@ -7,10 +7,10 @@ const PropTypes = require('prop-types')
|
||||
const h = require('react-hyperscript')
|
||||
const actions = require('../../actions')
|
||||
const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
|
||||
const Identicon = require('../identicon')
|
||||
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
|
||||
const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
|
||||
const Tooltip = require('../tooltip')
|
||||
import Identicon from '../identicon'
|
||||
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
|
||||
import { PRIMARY } from '../../constants/common'
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const Identicon = require('./identicon')
|
||||
import Identicon from './identicon'
|
||||
const formatBalance = require('../util').formatBalance
|
||||
const addressSummary = require('../util').addressSummary
|
||||
|
||||
|
@ -2,13 +2,13 @@ import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import { matchPath } from 'react-router-dom'
|
||||
import Identicon from '../identicon'
|
||||
|
||||
const {
|
||||
ENVIRONMENT_TYPE_NOTIFICATION,
|
||||
ENVIRONMENT_TYPE_POPUP,
|
||||
} = require('../../../../app/scripts/lib/enums')
|
||||
const { DEFAULT_ROUTE, INITIALIZE_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('../../routes')
|
||||
const Identicon = require('../identicon')
|
||||
const NetworkIndicator = require('../network')
|
||||
|
||||
export default class AppHeader extends PureComponent {
|
||||
|
@ -2,11 +2,11 @@ const Component = require('react').Component
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const TokenBalance = require('./token-balance')
|
||||
const Identicon = require('./identicon')
|
||||
import TokenBalance from './token-balance'
|
||||
import Identicon from './identicon'
|
||||
import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display'
|
||||
import { PRIMARY, SECONDARY } from '../constants/common'
|
||||
const { getAssetImages, conversionRateSelector, getCurrentCurrency} = require('../selectors')
|
||||
const { getNativeCurrency, getAssetImages, conversionRateSelector, getCurrentCurrency} = require('../selectors')
|
||||
|
||||
const { formatBalance } = require('../util')
|
||||
|
||||
@ -21,6 +21,7 @@ function mapStateToProps (state) {
|
||||
return {
|
||||
account,
|
||||
network,
|
||||
nativeCurrency: getNativeCurrency(state),
|
||||
conversionRate: conversionRateSelector(state),
|
||||
currentCurrency: getCurrentCurrency(state),
|
||||
assetImages: getAssetImages(state),
|
||||
@ -66,10 +67,10 @@ BalanceComponent.prototype.renderTokenBalance = function () {
|
||||
|
||||
BalanceComponent.prototype.renderBalance = function () {
|
||||
const props = this.props
|
||||
const { account } = props
|
||||
const { account, nativeCurrency } = props
|
||||
const balanceValue = account && account.balance
|
||||
const needsParse = 'needsParse' in props ? props.needsParse : true
|
||||
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...'
|
||||
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse, nativeCurrency) : '...'
|
||||
const showFiat = 'showFiat' in props ? props.showFiat : true
|
||||
|
||||
if (formattedBalance === 'None' || formattedBalance === '...') {
|
||||
@ -81,11 +82,12 @@ BalanceComponent.prototype.renderBalance = function () {
|
||||
}
|
||||
|
||||
return h('div.flex-column.balance-display', {}, [
|
||||
h('div.token-amount', {}, h(UserPreferencedCurrencyDisplay, {
|
||||
h(UserPreferencedCurrencyDisplay, {
|
||||
className: 'token-amount',
|
||||
value: balanceValue,
|
||||
type: PRIMARY,
|
||||
ethNumberOfDecimals: 3,
|
||||
})),
|
||||
}),
|
||||
|
||||
showFiat && h(UserPreferencedCurrencyDisplay, {
|
||||
value: balanceValue,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import { ETH, GWEI } from '../../constants/common'
|
||||
import { GWEI } from '../../constants/common'
|
||||
|
||||
export default class CurrencyDisplay extends PureComponent {
|
||||
static propTypes = {
|
||||
@ -10,8 +10,9 @@ export default class CurrencyDisplay extends PureComponent {
|
||||
prefix: PropTypes.string,
|
||||
prefixComponent: PropTypes.node,
|
||||
style: PropTypes.object,
|
||||
suffix: PropTypes.string,
|
||||
// Used in container
|
||||
currency: PropTypes.oneOf([ETH]),
|
||||
currency: PropTypes.string,
|
||||
denomination: PropTypes.oneOf([GWEI]),
|
||||
value: PropTypes.string,
|
||||
numberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
@ -19,17 +20,25 @@ export default class CurrencyDisplay extends PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, displayValue, prefix, prefixComponent, style } = this.props
|
||||
const { className, displayValue, prefix, prefixComponent, style, suffix } = this.props
|
||||
const text = `${prefix || ''}${displayValue}`
|
||||
const title = `${text} ${suffix}`
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames('currency-display-component', className)}
|
||||
style={style}
|
||||
title={text}
|
||||
title={title}
|
||||
>
|
||||
{ prefixComponent}
|
||||
<span className="currency-display-component__text">{ text }</span>
|
||||
{
|
||||
suffix && (
|
||||
<span className="currency-display-component__suffix">
|
||||
{ suffix }
|
||||
</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -3,16 +3,17 @@ import CurrencyDisplay from './currency-display.component'
|
||||
import { getValueFromWeiHex, formatCurrency } from '../../helpers/confirm-transaction/util'
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { metamask: { currentCurrency, conversionRate } } = state
|
||||
const { metamask: { nativeCurrency, currentCurrency, conversionRate } } = state
|
||||
|
||||
return {
|
||||
currentCurrency,
|
||||
conversionRate,
|
||||
nativeCurrency,
|
||||
}
|
||||
}
|
||||
|
||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
const { currentCurrency, conversionRate, ...restStateProps } = stateProps
|
||||
const { nativeCurrency, currentCurrency, conversionRate, ...restStateProps } = stateProps
|
||||
const {
|
||||
value,
|
||||
numberOfDecimals = 2,
|
||||
@ -24,16 +25,17 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
|
||||
const toCurrency = currency || currentCurrency
|
||||
const convertedValue = getValueFromWeiHex({
|
||||
value, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination,
|
||||
value, fromCurrency: nativeCurrency, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination,
|
||||
})
|
||||
const formattedValue = formatCurrency(convertedValue, toCurrency)
|
||||
const displayValue = hideLabel ? formattedValue : `${formattedValue} ${toCurrency.toUpperCase()}`
|
||||
const displayValue = formatCurrency(convertedValue, toCurrency)
|
||||
const suffix = hideLabel ? undefined : toCurrency.toUpperCase()
|
||||
|
||||
return {
|
||||
...restStateProps,
|
||||
...dispatchProps,
|
||||
...restOwnProps,
|
||||
displayValue,
|
||||
suffix,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,4 +7,8 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__suffix {
|
||||
padding-left: 4px;
|
||||
}
|
||||
}
|
||||
|
@ -20,12 +20,14 @@ describe('CurrencyDisplay container', () => {
|
||||
metamask: {
|
||||
conversionRate: 280.45,
|
||||
currentCurrency: 'usd',
|
||||
nativeCurrency: 'ETH',
|
||||
},
|
||||
}
|
||||
|
||||
assert.deepEqual(mapStateToProps(mockState), {
|
||||
conversionRate: 280.45,
|
||||
currentCurrency: 'usd',
|
||||
nativeCurrency: 'ETH',
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -35,6 +37,7 @@ describe('CurrencyDisplay container', () => {
|
||||
const mockStateProps = {
|
||||
conversionRate: 280.45,
|
||||
currentCurrency: 'usd',
|
||||
nativeCurrency: 'ETH',
|
||||
}
|
||||
|
||||
const tests = [
|
||||
@ -43,71 +46,93 @@ describe('CurrencyDisplay container', () => {
|
||||
value: '0x2386f26fc10000',
|
||||
numberOfDecimals: 2,
|
||||
currency: 'usd',
|
||||
nativeCurrency: 'ETH',
|
||||
},
|
||||
result: {
|
||||
displayValue: '$2.80 USD',
|
||||
displayValue: '$2.80',
|
||||
suffix: 'USD',
|
||||
nativeCurrency: 'ETH',
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x2386f26fc10000',
|
||||
currency: 'usd',
|
||||
nativeCurrency: 'ETH',
|
||||
},
|
||||
result: {
|
||||
displayValue: '$2.80 USD',
|
||||
displayValue: '$2.80',
|
||||
suffix: 'USD',
|
||||
nativeCurrency: 'ETH',
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x1193461d01595930',
|
||||
currency: 'ETH',
|
||||
nativeCurrency: 'ETH',
|
||||
numberOfDecimals: 3,
|
||||
},
|
||||
result: {
|
||||
displayValue: '1.266 ETH',
|
||||
displayValue: '1.266',
|
||||
suffix: 'ETH',
|
||||
nativeCurrency: 'ETH',
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x1193461d01595930',
|
||||
currency: 'ETH',
|
||||
nativeCurrency: 'ETH',
|
||||
numberOfDecimals: 3,
|
||||
hideLabel: true,
|
||||
},
|
||||
result: {
|
||||
nativeCurrency: 'ETH',
|
||||
displayValue: '1.266',
|
||||
suffix: undefined,
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x3b9aca00',
|
||||
currency: 'ETH',
|
||||
nativeCurrency: 'ETH',
|
||||
denomination: 'GWEI',
|
||||
hideLabel: true,
|
||||
},
|
||||
result: {
|
||||
nativeCurrency: 'ETH',
|
||||
displayValue: '1',
|
||||
suffix: undefined,
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x3b9aca00',
|
||||
currency: 'ETH',
|
||||
nativeCurrency: 'ETH',
|
||||
denomination: 'WEI',
|
||||
hideLabel: true,
|
||||
},
|
||||
result: {
|
||||
nativeCurrency: 'ETH',
|
||||
displayValue: '1000000000',
|
||||
suffix: undefined,
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x3b9aca00',
|
||||
currency: 'ETH',
|
||||
nativeCurrency: 'ETH',
|
||||
numberOfDecimals: 100,
|
||||
hideLabel: true,
|
||||
},
|
||||
result: {
|
||||
nativeCurrency: 'ETH',
|
||||
displayValue: '1e-9',
|
||||
suffix: undefined,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
@ -14,6 +14,7 @@ export default class CurrencyInput extends PureComponent {
|
||||
static propTypes = {
|
||||
conversionRate: PropTypes.number,
|
||||
currentCurrency: PropTypes.string,
|
||||
nativeCurrency: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
suffix: PropTypes.string,
|
||||
@ -77,13 +78,13 @@ export default class CurrencyInput extends PureComponent {
|
||||
}
|
||||
|
||||
renderConversionComponent () {
|
||||
const { useFiat, currentCurrency } = this.props
|
||||
const { useFiat, currentCurrency, nativeCurrency } = this.props
|
||||
const { hexValue } = this.state
|
||||
let currency, numberOfDecimals
|
||||
|
||||
if (useFiat) {
|
||||
// Display ETH
|
||||
currency = ETH
|
||||
currency = nativeCurrency || ETH
|
||||
numberOfDecimals = 6
|
||||
} else {
|
||||
// Display Fiat
|
||||
|
@ -3,18 +3,19 @@ import CurrencyInput from './currency-input.component'
|
||||
import { ETH } from '../../constants/common'
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { metamask: { currentCurrency, conversionRate } } = state
|
||||
const { metamask: { nativeCurrency, currentCurrency, conversionRate } } = state
|
||||
|
||||
return {
|
||||
nativeCurrency,
|
||||
currentCurrency,
|
||||
conversionRate,
|
||||
}
|
||||
}
|
||||
|
||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
const { currentCurrency } = stateProps
|
||||
const { nativeCurrency, currentCurrency } = stateProps
|
||||
const { useFiat } = ownProps
|
||||
const suffix = useFiat ? currentCurrency.toUpperCase() : ETH
|
||||
const suffix = useFiat ? currentCurrency.toUpperCase() : nativeCurrency || ETH
|
||||
|
||||
return {
|
||||
...stateProps,
|
||||
|
@ -22,6 +22,7 @@ describe('CurrencyInput Component', () => {
|
||||
it('should render properly with a suffix', () => {
|
||||
const mockStore = {
|
||||
metamask: {
|
||||
nativeCurrency: 'ETH',
|
||||
currentCurrency: 'usd',
|
||||
conversionRate: 231.06,
|
||||
},
|
||||
@ -32,6 +33,7 @@ describe('CurrencyInput Component', () => {
|
||||
<Provider store={store}>
|
||||
<CurrencyInput
|
||||
suffix="ETH"
|
||||
nativeCurrency="ETH"
|
||||
/>
|
||||
</Provider>
|
||||
)
|
||||
@ -45,6 +47,7 @@ describe('CurrencyInput Component', () => {
|
||||
it('should render properly with an ETH value', () => {
|
||||
const mockStore = {
|
||||
metamask: {
|
||||
nativeCurrency: 'ETH',
|
||||
currentCurrency: 'usd',
|
||||
conversionRate: 231.06,
|
||||
},
|
||||
@ -56,6 +59,7 @@ describe('CurrencyInput Component', () => {
|
||||
<CurrencyInput
|
||||
value="de0b6b3a7640000"
|
||||
suffix="ETH"
|
||||
nativeCurrency="ETH"
|
||||
currentCurrency="usd"
|
||||
conversionRate={231.06}
|
||||
/>
|
||||
@ -75,6 +79,7 @@ describe('CurrencyInput Component', () => {
|
||||
it('should render properly with a fiat value', () => {
|
||||
const mockStore = {
|
||||
metamask: {
|
||||
nativeCurrency: 'ETH',
|
||||
currentCurrency: 'usd',
|
||||
conversionRate: 231.06,
|
||||
},
|
||||
@ -87,6 +92,7 @@ describe('CurrencyInput Component', () => {
|
||||
value="f602f2234d0ea"
|
||||
suffix="USD"
|
||||
useFiat
|
||||
nativeCurrency="ETH"
|
||||
currentCurrency="usd"
|
||||
conversionRate={231.06}
|
||||
/>
|
||||
@ -116,6 +122,7 @@ describe('CurrencyInput Component', () => {
|
||||
it('should call onChange and onBlur on input changes with the hex value for ETH', () => {
|
||||
const mockStore = {
|
||||
metamask: {
|
||||
nativeCurrency: 'ETH',
|
||||
currentCurrency: 'usd',
|
||||
conversionRate: 231.06,
|
||||
},
|
||||
@ -127,6 +134,7 @@ describe('CurrencyInput Component', () => {
|
||||
onChange={handleChangeSpy}
|
||||
onBlur={handleBlurSpy}
|
||||
suffix="ETH"
|
||||
nativeCurrency="ETH"
|
||||
currentCurrency="usd"
|
||||
conversionRate={231.06}
|
||||
/>
|
||||
@ -160,6 +168,7 @@ describe('CurrencyInput Component', () => {
|
||||
it('should call onChange and onBlur on input changes with the hex value for fiat', () => {
|
||||
const mockStore = {
|
||||
metamask: {
|
||||
nativeCurrency: 'ETH',
|
||||
currentCurrency: 'usd',
|
||||
conversionRate: 231.06,
|
||||
},
|
||||
@ -171,6 +180,7 @@ describe('CurrencyInput Component', () => {
|
||||
onChange={handleChangeSpy}
|
||||
onBlur={handleBlurSpy}
|
||||
suffix="USD"
|
||||
nativeCurrency="ETH"
|
||||
currentCurrency="usd"
|
||||
conversionRate={231.06}
|
||||
useFiat
|
||||
@ -205,6 +215,7 @@ describe('CurrencyInput Component', () => {
|
||||
it('should change the state and pass in a new decimalValue when props.value changes', () => {
|
||||
const mockStore = {
|
||||
metamask: {
|
||||
nativeCurrency: 'ETH',
|
||||
currentCurrency: 'usd',
|
||||
conversionRate: 231.06,
|
||||
},
|
||||
@ -216,6 +227,7 @@ describe('CurrencyInput Component', () => {
|
||||
onChange={handleChangeSpy}
|
||||
onBlur={handleBlurSpy}
|
||||
suffix="USD"
|
||||
nativeCurrency="ETH"
|
||||
currentCurrency="usd"
|
||||
conversionRate={231.06}
|
||||
useFiat
|
||||
|
@ -20,12 +20,14 @@ describe('CurrencyInput container', () => {
|
||||
metamask: {
|
||||
conversionRate: 280.45,
|
||||
currentCurrency: 'usd',
|
||||
nativeCurrency: 'ETH',
|
||||
},
|
||||
}
|
||||
|
||||
assert.deepEqual(mapStateToProps(mockState), {
|
||||
conversionRate: 280.45,
|
||||
currentCurrency: 'usd',
|
||||
nativeCurrency: 'ETH',
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -35,12 +37,14 @@ describe('CurrencyInput container', () => {
|
||||
const mockStateProps = {
|
||||
conversionRate: 280.45,
|
||||
currentCurrency: 'usd',
|
||||
nativeCurrency: 'ETH',
|
||||
}
|
||||
const mockDispatchProps = {}
|
||||
|
||||
assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, { useFiat: true }), {
|
||||
conversionRate: 280.45,
|
||||
currentCurrency: 'usd',
|
||||
nativeCurrency: 'ETH',
|
||||
useFiat: true,
|
||||
suffix: 'USD',
|
||||
})
|
||||
@ -48,6 +52,7 @@ describe('CurrencyInput container', () => {
|
||||
assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, {}), {
|
||||
conversionRate: 280.45,
|
||||
currentCurrency: 'usd',
|
||||
nativeCurrency: 'ETH',
|
||||
suffix: 'ETH',
|
||||
})
|
||||
})
|
||||
|
@ -6,7 +6,7 @@ const genAccountLink = require('../../../../lib/account-link.js')
|
||||
const connect = require('react-redux').connect
|
||||
const Dropdown = require('./dropdown').Dropdown
|
||||
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
|
||||
const Identicon = require('../../identicon')
|
||||
import Identicon from '../../identicon'
|
||||
const { checksumAddress } = require('../../../util')
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
const { formatBalance } = require('../../../util')
|
||||
@ -26,14 +26,14 @@ class AccountDropdowns extends Component {
|
||||
}
|
||||
|
||||
renderAccounts () {
|
||||
const { identities, accounts, selected, menuItemStyles, actions, keyrings } = this.props
|
||||
const { identities, accounts, selected, menuItemStyles, actions, keyrings, ticker } = this.props
|
||||
|
||||
return Object.keys(identities).map((key, index) => {
|
||||
const identity = identities[key]
|
||||
const isSelected = identity.address === selected
|
||||
|
||||
const balanceValue = accounts[key].balance
|
||||
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...'
|
||||
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, true, ticker) : '...'
|
||||
const simpleAddress = identity.address.substring(2).toLowerCase()
|
||||
|
||||
const keyring = keyrings.find((kr) => {
|
||||
@ -421,6 +421,7 @@ AccountDropdowns.propTypes = {
|
||||
network: PropTypes.number,
|
||||
// actions.showExportPrivateKeyModal: ,
|
||||
style: PropTypes.object,
|
||||
ticker: PropTypes.string,
|
||||
enableAccountsSelector: PropTypes.bool,
|
||||
enableAccountOption: PropTypes.bool,
|
||||
enableAccountOptions: PropTypes.bool,
|
||||
@ -458,6 +459,7 @@ const mapDispatchToProps = (dispatch) => {
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
ticker: state.metamask.ticker,
|
||||
keyrings: state.metamask.keyrings,
|
||||
sidebarOpen: state.appState.sidebar.isOpen,
|
||||
}
|
||||
|
@ -24,8 +24,9 @@ const notToggleElementClassnames = [
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
provider: state.metamask.provider,
|
||||
frequentRpcList: state.metamask.frequentRpcList || [],
|
||||
frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
|
||||
networkDropdownOpen: state.appState.networkDropdownOpen,
|
||||
network: state.metamask.network,
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,8 +41,8 @@ function mapDispatchToProps (dispatch) {
|
||||
setDefaultRpcTarget: type => {
|
||||
dispatch(actions.setDefaultRpcTarget(type))
|
||||
},
|
||||
setRpcTarget: (target) => {
|
||||
dispatch(actions.setRpcTarget(target))
|
||||
setRpcTarget: (target, network, ticker, nickname) => {
|
||||
dispatch(actions.setRpcTarget(target, network, ticker, nickname))
|
||||
},
|
||||
delRpcTarget: (target) => {
|
||||
dispatch(actions.delRpcTarget(target))
|
||||
@ -71,7 +72,7 @@ module.exports = compose(
|
||||
NetworkDropdown.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
|
||||
const rpcList = props.frequentRpcList
|
||||
const rpcListDetail = props.frequentRpcListDetail
|
||||
const isOpen = this.props.networkDropdownOpen
|
||||
const dropdownMenuItemStyle = {
|
||||
fontSize: '16px',
|
||||
@ -225,7 +226,7 @@ NetworkDropdown.prototype.render = function () {
|
||||
),
|
||||
|
||||
this.renderCustomOption(props.provider),
|
||||
this.renderCommonRpc(rpcList, props.provider),
|
||||
this.renderCommonRpc(rpcListDetail, props.provider),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
@ -267,28 +268,33 @@ NetworkDropdown.prototype.getNetworkName = function () {
|
||||
} else if (providerName === 'rinkeby') {
|
||||
name = this.context.t('rinkeby')
|
||||
} else {
|
||||
name = this.context.t('unknownNetwork')
|
||||
name = provider.nickname || this.context.t('unknownNetwork')
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) {
|
||||
NetworkDropdown.prototype.renderCommonRpc = function (rpcListDetail, provider) {
|
||||
const props = this.props
|
||||
const reversedRpcList = rpcList.slice().reverse()
|
||||
const reversedRpcListDetail = rpcListDetail.slice().reverse()
|
||||
const network = props.network
|
||||
|
||||
return reversedRpcList.map((rpc) => {
|
||||
return reversedRpcListDetail.map((entry) => {
|
||||
const rpc = entry.rpcUrl
|
||||
const ticker = entry.ticker || 'ETH'
|
||||
const nickname = entry.nickname || ''
|
||||
const currentRpcTarget = provider.type === 'rpc' && rpc === provider.rpcTarget
|
||||
|
||||
if ((rpc === 'http://localhost:8545') || currentRpcTarget) {
|
||||
return null
|
||||
} else {
|
||||
const chainId = entry.chainId || network
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: `common${rpc}`,
|
||||
closeMenu: () => this.props.hideNetworkDropdown(),
|
||||
onClick: () => props.setRpcTarget(rpc),
|
||||
onClick: () => props.setRpcTarget(rpc, chainId, ticker, nickname),
|
||||
style: {
|
||||
fontSize: '16px',
|
||||
lineHeight: '20px',
|
||||
@ -302,7 +308,7 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) {
|
||||
style: {
|
||||
color: currentRpcTarget ? '#ffffff' : '#9b9b9b',
|
||||
},
|
||||
}, rpc),
|
||||
}, nickname || rpc),
|
||||
h('i.fa.fa-times.delete',
|
||||
{
|
||||
onClick: (e) => {
|
||||
@ -317,8 +323,9 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) {
|
||||
}
|
||||
|
||||
NetworkDropdown.prototype.renderCustomOption = function (provider) {
|
||||
const { rpcTarget, type } = provider
|
||||
const { rpcTarget, type, ticker, nickname } = provider
|
||||
const props = this.props
|
||||
const network = props.network
|
||||
|
||||
if (type !== 'rpc') return null
|
||||
|
||||
@ -332,7 +339,7 @@ NetworkDropdown.prototype.renderCustomOption = function (provider) {
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: rpcTarget,
|
||||
onClick: () => props.setRpcTarget(rpcTarget),
|
||||
onClick: () => props.setRpcTarget(rpcTarget, network, ticker, nickname),
|
||||
closeMenu: () => this.props.hideNetworkDropdown(),
|
||||
style: {
|
||||
fontSize: '16px',
|
||||
@ -347,7 +354,7 @@ NetworkDropdown.prototype.renderCustomOption = function (provider) {
|
||||
style: {
|
||||
color: '#ffffff',
|
||||
},
|
||||
}, rpcTarget),
|
||||
}, nickname || rpcTarget),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
@ -45,8 +45,8 @@ describe('Network Dropdown', () => {
|
||||
provider: {
|
||||
'type': 'test',
|
||||
},
|
||||
frequentRpcList: [
|
||||
'http://localhost:7545',
|
||||
frequentRpcListDetail: [
|
||||
{ rpcUrl: 'http://localhost:7545' },
|
||||
],
|
||||
},
|
||||
appState: {
|
||||
|
@ -1,5 +1,6 @@
|
||||
const { Component } = require('react')
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const { inherits } = require('util')
|
||||
const {
|
||||
formatBalance,
|
||||
@ -8,7 +9,12 @@ const {
|
||||
const Tooltip = require('./tooltip.js')
|
||||
const FiatValue = require('./fiat-value.js')
|
||||
|
||||
module.exports = EthBalanceComponent
|
||||
module.exports = connect(mapStateToProps)(EthBalanceComponent)
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
ticker: state.metamask.ticker,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(EthBalanceComponent, Component)
|
||||
function EthBalanceComponent () {
|
||||
@ -17,9 +23,9 @@ function EthBalanceComponent () {
|
||||
|
||||
EthBalanceComponent.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { value, style, width, needsParse = true } = props
|
||||
const { ticker, value, style, width, needsParse = true } = props
|
||||
|
||||
const formattedValue = value ? formatBalance(value, 6, needsParse) : '...'
|
||||
const formattedValue = value ? formatBalance(value, 6, needsParse, ticker) : '...'
|
||||
|
||||
return (
|
||||
|
||||
|
@ -1,124 +0,0 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const isNode = require('detect-node')
|
||||
const findDOMNode = require('react-dom').findDOMNode
|
||||
const jazzicon = require('jazzicon')
|
||||
const iconFactoryGen = require('../../lib/icon-factory')
|
||||
const iconFactory = iconFactoryGen(jazzicon)
|
||||
const { toDataUrl } = require('../../lib/blockies')
|
||||
|
||||
module.exports = connect(mapStateToProps)(IdenticonComponent)
|
||||
|
||||
inherits(IdenticonComponent, Component)
|
||||
function IdenticonComponent () {
|
||||
Component.call(this)
|
||||
|
||||
this.defaultDiameter = 46
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
useBlockie: state.metamask.useBlockie,
|
||||
}
|
||||
}
|
||||
|
||||
IdenticonComponent.prototype.render = function () {
|
||||
var props = this.props
|
||||
const { className = '', address, image } = props
|
||||
var diameter = props.diameter || this.defaultDiameter
|
||||
const style = {
|
||||
height: diameter,
|
||||
width: diameter,
|
||||
borderRadius: diameter / 2,
|
||||
}
|
||||
if (image) {
|
||||
return h('img', {
|
||||
className: `${className} identicon`,
|
||||
src: image,
|
||||
style: {
|
||||
...style,
|
||||
},
|
||||
})
|
||||
} else if (address) {
|
||||
return h('div', {
|
||||
className: `${className} identicon`,
|
||||
key: 'identicon-' + address,
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexShrink: 0,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
...style,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
})
|
||||
} else {
|
||||
return h('img.balance-icon', {
|
||||
className,
|
||||
src: './images/eth_logo.svg',
|
||||
style: {
|
||||
...style,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
IdenticonComponent.prototype.componentDidMount = function () {
|
||||
var props = this.props
|
||||
const { address, useBlockie } = props
|
||||
|
||||
if (!address) return
|
||||
|
||||
if (!isNode) {
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var container = findDOMNode(this)
|
||||
|
||||
const diameter = props.diameter || this.defaultDiameter
|
||||
|
||||
if (useBlockie) {
|
||||
_generateBlockie(container, address, diameter)
|
||||
} else {
|
||||
_generateJazzicon(container, address, diameter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IdenticonComponent.prototype.componentDidUpdate = function () {
|
||||
var props = this.props
|
||||
const { address, useBlockie } = props
|
||||
|
||||
if (!address) return
|
||||
|
||||
if (!isNode) {
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var container = findDOMNode(this)
|
||||
|
||||
var children = container.children
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
container.removeChild(children[i])
|
||||
}
|
||||
|
||||
const diameter = props.diameter || this.defaultDiameter
|
||||
|
||||
if (useBlockie) {
|
||||
_generateBlockie(container, address, diameter)
|
||||
} else {
|
||||
_generateJazzicon(container, address, diameter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _generateBlockie (container, address, diameter) {
|
||||
const img = new Image()
|
||||
img.src = toDataUrl(address)
|
||||
img.height = diameter
|
||||
img.width = diameter
|
||||
container.appendChild(img)
|
||||
}
|
||||
|
||||
function _generateJazzicon (container, address, diameter) {
|
||||
const img = iconFactory.iconForAddress(address, diameter)
|
||||
container.appendChild(img)
|
||||
}
|
99
ui/app/components/identicon/identicon.component.js
Normal file
99
ui/app/components/identicon/identicon.component.js
Normal file
@ -0,0 +1,99 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import { toDataUrl } from '../../../lib/blockies'
|
||||
import contractMap from 'eth-contract-metadata'
|
||||
import { checksumAddress } from '../../../app/util'
|
||||
import Jazzicon from '../jazzicon'
|
||||
|
||||
const getStyles = diameter => (
|
||||
{
|
||||
height: diameter,
|
||||
width: diameter,
|
||||
borderRadius: diameter / 2,
|
||||
}
|
||||
)
|
||||
|
||||
export default class Identicon extends PureComponent {
|
||||
static propTypes = {
|
||||
address: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
diameter: PropTypes.number,
|
||||
image: PropTypes.string,
|
||||
useBlockie: PropTypes.bool,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
diameter: 46,
|
||||
}
|
||||
|
||||
renderImage () {
|
||||
const { className, diameter, image } = this.props
|
||||
|
||||
return (
|
||||
<img
|
||||
className={classnames('identicon', className)}
|
||||
src={image}
|
||||
style={getStyles(diameter)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderJazzicon () {
|
||||
const { address, className, diameter } = this.props
|
||||
|
||||
return (
|
||||
<Jazzicon
|
||||
address={address}
|
||||
diameter={diameter}
|
||||
className={classnames('identicon', className)}
|
||||
style={getStyles(diameter)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderBlockie () {
|
||||
const { address, className, diameter } = this.props
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames('identicon', className)}
|
||||
style={getStyles(diameter)}
|
||||
>
|
||||
<img
|
||||
src={toDataUrl(address)}
|
||||
height={diameter}
|
||||
width={diameter}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, address, image, diameter, useBlockie } = this.props
|
||||
|
||||
if (image) {
|
||||
return this.renderImage()
|
||||
}
|
||||
|
||||
if (address) {
|
||||
const checksummedAddress = checksumAddress(address)
|
||||
|
||||
if (contractMap[checksummedAddress] && contractMap[checksummedAddress].logo) {
|
||||
return this.renderJazzicon()
|
||||
}
|
||||
|
||||
return useBlockie
|
||||
? this.renderBlockie()
|
||||
: this.renderJazzicon()
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
className={classnames('balance-icon', className)}
|
||||
src="./images/eth_logo.svg"
|
||||
style={getStyles(diameter)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user