1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Merge branch 'develop' into develop

This commit is contained in:
kumavis 2018-10-20 23:48:10 -04:00 committed by GitHub
commit 3b46478024
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
160 changed files with 8611 additions and 2452 deletions

View File

@ -36,6 +36,10 @@ workflows:
requires: requires:
- prep-deps-npm - prep-deps-npm
- prep-build - prep-build
- test-e2e-beta-drizzle:
requires:
- prep-deps-npm
- prep-build
- test-unit: - test-unit:
requires: requires:
- prep-deps-npm - prep-deps-npm
@ -68,6 +72,7 @@ workflows:
- test-e2e-firefox - test-e2e-firefox
- test-e2e-beta-chrome - test-e2e-beta-chrome
- test-e2e-beta-firefox - test-e2e-beta-firefox
- test-e2e-beta-drizzle
- test-integration-mascara-chrome - test-integration-mascara-chrome
- test-integration-mascara-firefox - test-integration-mascara-firefox
- test-integration-flat-chrome - test-integration-flat-chrome
@ -222,6 +227,19 @@ jobs:
path: test-artifacts path: test-artifacts
destination: test-artifacts destination: test-artifacts
test-e2e-beta-drizzle:
docker:
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- attach_workspace:
at: .
- run:
name: test:e2e:drizzle:beta
command: npm run test:e2e:drizzle:beta
- store_artifacts:
path: test-artifacts
destination: test-artifacts
test-e2e-beta-chrome: test-e2e-beta-chrome:
docker: docker:
- image: circleci/node:8.11.3-browsers - image: circleci/node:8.11.3-browsers

View File

@ -20,3 +20,4 @@ test/integration/bundle.js
test/integration/jquery-3.1.0.min.js test/integration/jquery-3.1.0.min.js
test/integration/helpers.js test/integration/helpers.js
test/integration/lib/first-time.js test/integration/lib/first-time.js

View File

@ -2,9 +2,34 @@
## Current Develop Branch ## Current Develop Branch
- [#5283](https://github.com/MetaMask/metamask-extension/pull/5283): Fix bug when eth.getCode() called with no contract
## 4.16.0 Wednesday October 17 2018
- Feature: Add toggle for primary currency (eth/fiat)
- Feature: add tooltip for view etherscan tx
- Feature: add Polish translations
- Feature: improve Korean translations
- Feature: improve Italian translations
- Bug Fix: Fix bug with "pending" block reference
- Bug Fix: Force AccountTracker to update balances on network change
- Bug Fix: Fix document extension check when injecting web3
- Bug Fix: Fix some support links
## 4.15.0 Thursday October 11 2018
- A rollback release, equivalent to `v4.11.1` to be deployed in the case that `v4.14.0` is found to have bugs.
## 4.14.0 Thursday October 11 2018
- Update transaction statuses when switching networks. - Update transaction statuses when switching networks.
- [#5470](https://github.com/MetaMask/metamask-extension/pull/5470) 100% coverage in French locale, fixed the procedure to verify proposed locale. - [#5470](https://github.com/MetaMask/metamask-extension/pull/5470) 100% coverage in French locale, fixed the procedure to verify proposed locale.
- [#5283](https://github.com/MetaMask/metamask-extension/pull/5283): Fix bug when eth.getCode() called with no contract - Added rudimentary support for the subscription API to support web3 1.0 and Truffle's Drizzle.
- [#5502](https://github.com/MetaMask/metamask-extension/pull/5502) Update Italian translation.
## 4.13.0
- A rollback release, equivalent to `v4.11.1` to be deployed in the case that `v4.12.0` is found to have bugs.
## 4.12.0 Thursday September 27 2018 ## 4.12.0 Thursday September 27 2018

View File

@ -1,5 +1,5 @@
# MetaMask Browser Extension # MetaMask Browser Extension
[![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master) [![Greenkeeper badge](https://badges.greenkeeper.io/MetaMask/metamask-extension.svg)](https://greenkeeper.io/) [![Stories in Ready](https://badge.waffle.io/MetaMask/metamask-extension.png?label=in%20progress&title=waffle.io)](https://waffle.io/MetaMask/metamask-extension) [![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master) [![Stories in Ready](https://badge.waffle.io/MetaMask/metamask-extension.png?label=in%20progress&title=waffle.io)](https://waffle.io/MetaMask/metamask-extension)
## Support ## Support

View File

@ -14,6 +14,9 @@
"accountName": { "accountName": {
"message": "Account Name" "message": "Account Name"
}, },
"accountOptions": {
"message": "Account Options"
},
"accountSelectionRequired": { "accountSelectionRequired": {
"message": "You need to select an account!" "message": "You need to select an account!"
}, },
@ -137,6 +140,9 @@
"clickCopy": { "clickCopy": {
"message": "Click to Copy" "message": "Click to Copy"
}, },
"clickToAdd": {
"message": "Click on $1 to add them to your account"
},
"close": { "close": {
"message": "Close" "message": "Close"
}, },
@ -358,12 +364,18 @@
"enterPasswordContinue": { "enterPasswordContinue": {
"message": "Enter password to continue" "message": "Enter password to continue"
}, },
"eth": {
"message": "ETH"
},
"etherscanView": { "etherscanView": {
"message": "View account on Etherscan" "message": "View account on Etherscan"
}, },
"exchangeRate": { "exchangeRate": {
"message": "Exchange Rate" "message": "Exchange Rate"
}, },
"expandView": {
"message": "Expand View"
},
"exportPrivateKey": { "exportPrivateKey": {
"message": "Export Private Key" "message": "Export Private Key"
}, },
@ -374,7 +386,7 @@
"message": "Failed" "message": "Failed"
}, },
"fiat": { "fiat": {
"message": "FIAT", "message": "Fiat",
"description": "Exchange type" "description": "Exchange type"
}, },
"fileImportFail": { "fileImportFail": {
@ -418,6 +430,9 @@
"gasLimitTooLow": { "gasLimitTooLow": {
"message": "Gas limit must be at least 21000" "message": "Gas limit must be at least 21000"
}, },
"gasUsed": {
"message": "Gas Used"
},
"generatingSeed": { "generatingSeed": {
"message": "Generating Seed..." "message": "Generating Seed..."
}, },
@ -629,6 +644,9 @@
"min": { "min": {
"message": "Minimum" "message": "Minimum"
}, },
"missingYourTokens": {
"message": "Don't see your tokens?"
},
"myAccounts": { "myAccounts": {
"message": "My Accounts" "message": "My Accounts"
}, },
@ -739,22 +757,22 @@
"parameters": { "parameters": {
"message": "Parameters" "message": "Parameters"
}, },
"passwordNotLongEnough": {
"message": "Password not long enough"
},
"passwordsDontMatch": {
"message": "Passwords Don't Match"
},
"password": { "password": {
"message": "Password" "message": "Password"
}, },
"passwordCorrect": { "passwordCorrect": {
"message": "Please make sure your password is correct." "message": "Please make sure your password is correct."
}, },
"passwordsDontMatch": {
"message": "Passwords Don't Match"
},
"passwordMismatch": { "passwordMismatch": {
"message": "passwords don't match", "message": "passwords don't match",
"description": "in password creation process, the two new password fields did not match" "description": "in password creation process, the two new password fields did not match"
}, },
"passwordNotLongEnough": {
"message": "Password not long enough"
},
"passwordShort": { "passwordShort": {
"message": "password not long enough", "message": "password not long enough",
"description": "in password creation process, the password is not long enough to be secure" "description": "in password creation process, the password is not long enough to be secure"
@ -781,6 +799,12 @@
"prev": { "prev": {
"message": "Prev" "message": "Prev"
}, },
"primaryCurrencySetting": {
"message": "Primary Currency"
},
"primaryCurrencySettingDescription": {
"message": "Select ETH to prioritize displaying values in ETH. Select Fiat to prioritize displaying values in your selected currency."
},
"privacyMsg": { "privacyMsg": {
"message": "Privacy Policy" "message": "Privacy Policy"
}, },
@ -789,7 +813,7 @@
"description": "select this type of file to use to import an account" "description": "select this type of file to use to import an account"
}, },
"privateKeyWarning": { "privateKeyWarning": {
"message": "Warning: Never disclose this key. Anyone with your private keys can take steal any assets held in your account." "message": "Warning: Never disclose this key. Anyone with your private keys can steal any assets held in your account."
}, },
"privateNetwork": { "privateNetwork": {
"message": "Private Network" "message": "Private Network"
@ -857,9 +881,6 @@
"retryWithMoreGas": { "retryWithMoreGas": {
"message": "Retry with a higher gas price here" "message": "Retry with a higher gas price here"
}, },
"walletSeed": {
"message": "Wallet Seed"
},
"restore": { "restore": {
"message": "Restore" "message": "Restore"
}, },
@ -1186,7 +1207,7 @@
"message": "These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret." "message": "These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret."
}, },
"typePassword": { "typePassword": {
"message": "Type Your Password" "message": "Type your MetaMask password"
}, },
"uiWelcome": { "uiWelcome": {
"message": "Welcome to the New UI (Beta)" "message": "Welcome to the New UI (Beta)"
@ -1222,7 +1243,7 @@
"message": "Ooops! Something went wrong...." "message": "Ooops! Something went wrong...."
}, },
"unknownCameraError": { "unknownCameraError": {
"message": "There was an error while trying to access you camera. Please try again..." "message": "There was an error while trying to access your camera. Please try again..."
}, },
"unlock": { "unlock": {
"message": "Unlock" "message": "Unlock"
@ -1261,6 +1282,9 @@
"visitWebSite": { "visitWebSite": {
"message": "Visit our web site" "message": "Visit our web site"
}, },
"walletSeed": {
"message": "Wallet Seed"
},
"warning": { "warning": {
"message": "Warning" "message": "Warning"
}, },

View File

@ -156,7 +156,7 @@
"message": " Copiar " "message": " Copiar "
}, },
"copyPrivateKey": { "copyPrivateKey": {
"message": "Ésta es tu llave privada (haz click para copiar)" "message": "Ésta es tu clave privada (haz click para copiar)"
}, },
"copyToClipboard": { "copyToClipboard": {
"message": "Copiar al portapapeles" "message": "Copiar al portapapeles"
@ -278,10 +278,10 @@
"message": "Tipo de cambio" "message": "Tipo de cambio"
}, },
"exportPrivateKey": { "exportPrivateKey": {
"message": "Exportar llave privada" "message": "Exportar clave privada"
}, },
"exportPrivateKeyWarning": { "exportPrivateKeyWarning": {
"message": "Exportar llaves privadas bajo TU PROPIO riesgo" "message": "Exportar claves privadas bajo TU PROPIO riesgo"
}, },
"failed": { "failed": {
"message": "Fallo" "message": "Fallo"
@ -579,8 +579,8 @@
"description": "En el proceso de creación de contraseña, esta no es lo suficientemente larga para ser segura" "description": "En el proceso de creación de contraseña, esta no es lo suficientemente larga para ser segura"
}, },
"pastePrivateKey": { "pastePrivateKey": {
"message": "Pega tu llave privada aqui", "message": "Pega tu clave privada aqui",
"description": "Para importar una cuenta desde una llave privada" "description": "Para importar una cuenta desde una clave privada"
}, },
"pasteSeed": { "pasteSeed": {
"message": "¡Pega tu frase semilla aquí!" "message": "¡Pega tu frase semilla aquí!"
@ -595,7 +595,7 @@
"message": "Política de privacidad" "message": "Política de privacidad"
}, },
"privateKey": { "privateKey": {
"message": "Llave privada", "message": "Clave privada",
"description": "Selecciona este tupo de archivo para importar una cuenta" "description": "Selecciona este tupo de archivo para importar una cuenta"
}, },
"privateKeyWarning": { "privateKeyWarning": {
@ -712,7 +712,7 @@
"message": "Comprar con ShapeShift" "message": "Comprar con ShapeShift"
}, },
"showPrivateKeys": { "showPrivateKeys": {
"message": "Mostrar llaves privadas" "message": "Mostrar claves privadas"
}, },
"showQRCode": { "showQRCode": {
"message": "Mostrar codigo QR" "message": "Mostrar codigo QR"

View File

@ -171,7 +171,7 @@
"message": "Connection au réseau de test Kovan" "message": "Connection au réseau de test Kovan"
}, },
"connectingToMainnet": { "connectingToMainnet": {
"message": "Connection au Réseau principal Ethereum" "message": "Connection au réseau principal Ethereum"
}, },
"connectingToRopsten": { "connectingToRopsten": {
"message": "Connection au réseau de test Ropsten" "message": "Connection au réseau de test Ropsten"
@ -350,13 +350,13 @@
"message": "Nom ENS inconnu" "message": "Nom ENS inconnu"
}, },
"enterPassword": { "enterPassword": {
"message": "Entrer le mot de passe" "message": "Entrez votre mot de passe"
}, },
"enterPasswordConfirm": { "enterPasswordConfirm": {
"message": "Enter your password to confirm" "message": "Entrez votre mot de passe pour confirmer"
}, },
"enterPasswordContinue": { "enterPasswordContinue": {
"message": "Enter password to continue" "message": "Entrez votre mot de passe pour continuer"
}, },
"etherscanView": { "etherscanView": {
"message": "Afficher le compte sur Etherscan" "message": "Afficher le compte sur Etherscan"

View File

@ -11,6 +11,7 @@
{ "code": "ko", "name": "Korean" }, { "code": "ko", "name": "Korean" },
{ "code": "nl", "name": "Dutch" }, { "code": "nl", "name": "Dutch" },
{ "code": "ph", "name": "Tagalog" }, { "code": "ph", "name": "Tagalog" },
{ "code": "pl", "name": "Polish" },
{ "code": "pt", "name": "Portuguese" }, { "code": "pt", "name": "Portuguese" },
{ "code": "ru", "name": "Russian" }, { "code": "ru", "name": "Russian" },
{ "code": "sl", "name": "Slovenian" }, { "code": "sl", "name": "Slovenian" },

View File

@ -2,6 +2,9 @@
"accept": { "accept": {
"message": "Accetta" "message": "Accetta"
}, },
"accessingYourCamera": {
"message": "Accesso alla fotocamera..."
},
"account": { "account": {
"message": "Account" "message": "Account"
}, },
@ -11,6 +14,15 @@
"accountName": { "accountName": {
"message": "Nome Account" "message": "Nome Account"
}, },
"accountOptions": {
"message": "Account Options"
},
"accountSelectionRequired": {
"message": "Devi selezionare un account!"
},
"activityLog": {
"message": "log attività"
},
"address": { "address": {
"message": "Indirizzo" "message": "Indirizzo"
}, },
@ -23,6 +35,12 @@
"addTokens": { "addTokens": {
"message": "Aggiungi più token" "message": "Aggiungi più token"
}, },
"addSuggestedTokens": {
"message": "Aggiungi Token Suggeriti"
},
"addAcquiredTokens": {
"message": "Aggiungi i token che hai acquistato usando MetaMask"
},
"amount": { "amount": {
"message": "Importo" "message": "Importo"
}, },
@ -37,9 +55,21 @@
"message": "MetaMask", "message": "MetaMask",
"description": "Il nome dell'applicazione" "description": "Il nome dell'applicazione"
}, },
"approve": {
"message": "Approva"
},
"approved": {
"message": "Approvato"
},
"attemptingConnect": { "attemptingConnect": {
"message": "Tentativo di connessione alla blockchain." "message": "Tentativo di connessione alla blockchain."
}, },
"attemptToCancel": {
"message": "Tentativo di Annullamento?"
},
"attemptToCancelDescription": {
"message": "Tentare di annullare non garantisce che la transazione verrà annullata. Se annullata, è comunque richiesto pagare alla rete la commissione sulla transazione."
},
"attributions": { "attributions": {
"message": "Attribuzioni" "message": "Attribuzioni"
}, },
@ -71,8 +101,11 @@
"borrowDharma": { "borrowDharma": {
"message": "Prendi in presisito con Dharma (Beta)" "message": "Prendi in presisito con Dharma (Beta)"
}, },
"browserNotSupported": {
"message": "Il tuo Browser non è supportato..."
},
"builtInCalifornia": { "builtInCalifornia": {
"message": "MetaMask è progettato e costruito in California." "message": "MetaMask è progettato e realizzato in California."
}, },
"buy": { "buy": {
"message": "Compra" "message": "Compra"
@ -83,8 +116,23 @@
"buyCoinbaseExplainer": { "buyCoinbaseExplainer": {
"message": "Coinbase è il servizio più popolare al mondo per comprare e vendere Bitcoin, Ethereum e Litecoin." "message": "Coinbase è il servizio più popolare al mondo per comprare e vendere Bitcoin, Ethereum e Litecoin."
}, },
"bytes": {
"message": "Bytes"
},
"ok": {
"message": "Ok"
},
"cancel": { "cancel": {
"message": "Cancella" "message": "Annulla"
},
"cancelAttempt": {
"message": "Tentativo di Annullamento"
},
"cancellationGasFee": {
"message": "Commissione di Annullamento in Gas"
},
"cancelN": {
"message": "Cancel all $1 transactions"
}, },
"classicInterface": { "classicInterface": {
"message": "Usa l'interfaccia classica" "message": "Usa l'interfaccia classica"
@ -92,9 +140,18 @@
"clickCopy": { "clickCopy": {
"message": "Clicca per Copiare" "message": "Clicca per Copiare"
}, },
"close": {
"message": "Chiudi"
},
"chromeRequiredForHardwareWallets": {
"message": "Devi usare MetaMask con Google Chrome per connettere il tuo Portafoglio Hardware"
},
"confirm": { "confirm": {
"message": "Conferma" "message": "Conferma"
}, },
"confirmed": {
"message": "Confermata"
},
"confirmContract": { "confirmContract": {
"message": "Conferma Contratto" "message": "Conferma Contratto"
}, },
@ -104,6 +161,36 @@
"confirmTransaction": { "confirmTransaction": {
"message": "Conferma Transazione" "message": "Conferma Transazione"
}, },
"connectHardwareWallet": {
"message": "Connetti Portafoglio Hardware"
},
"connect": {
"message": "Connetti"
},
"connecting": {
"message": "Connessione..."
},
"connectingToKovan": {
"message": "Connessione alla Rete di test Kovan"
},
"connectingToMainnet": {
"message": "Connessione alla Rete Ethereum Principale"
},
"connectingToRopsten": {
"message": "Connessione alla Rete di test Ropsten"
},
"connectingToRinkeby": {
"message": "Connessione alla Rete di test Rinkeby"
},
"connectingToUnknown": {
"message": "Connessione ad una Rete Sconosciuta"
},
"connectToLedger": {
"message": "Connettersi al Ledger"
},
"connectToTrezor": {
"message": "Connettersi al Trezor"
},
"continue": { "continue": {
"message": "Continua" "message": "Continua"
}, },
@ -131,6 +218,9 @@
"copy": { "copy": {
"message": "Copia" "message": "Copia"
}, },
"copyAddress": {
"message": "Copia l'indirizzo"
},
"copyToClipboard": { "copyToClipboard": {
"message": "Copia negli appunti" "message": "Copia negli appunti"
}, },
@ -156,12 +246,21 @@
"currentConversion": { "currentConversion": {
"message": "Cambio Corrente" "message": "Cambio Corrente"
}, },
"currentLanguage": {
"message": "Lingua Corrente"
},
"currentNetwork": { "currentNetwork": {
"message": "Rete Corrente" "message": "Rete Corrente"
}, },
"currentRpc": {
"message": "RPC Corrente"
},
"customGas": { "customGas": {
"message": "Personalizza Gas" "message": "Personalizza Gas"
}, },
"customToken": {
"message": "Token Personalizzato"
},
"customize": { "customize": {
"message": "Personalizza" "message": "Personalizza"
}, },
@ -223,33 +322,54 @@
"done": { "done": {
"message": "Finito" "message": "Finito"
}, },
"downloadGoogleChrome": {
"message": "Scarica Google Chrome"
},
"downloadStateLogs": { "downloadStateLogs": {
"message": "Scarica i log di Stato" "message": "Scarica i log di Stato"
}, },
"dontHaveAHardwareWallet": {
"message": "Non hai un portafoglio hardware?"
},
"dropped": {
"message": "Abbandonata"
},
"edit": { "edit": {
"message": "Modifica" "message": "Modifica"
}, },
"editAccountName": { "editAccountName": {
"message": "Modifica Nome Account" "message": "Modifica Nome Account"
}, },
"editingTransaction": {
"message": "Modifica la transazione"
},
"emailUs": { "emailUs": {
"message": "Mandaci una mail!" "message": "Mandaci una mail!"
}, },
"encryptNewDen": { "encryptNewDen": {
"message": "Cripta il tuo nuovo DEN" "message": "Cripta il tuo nuovo DEN"
}, },
"ensNameNotFound": {
"message": "Nome ENS non trovato"
},
"enterPassword": { "enterPassword": {
"message": "Inserisci password" "message": "Inserisci password"
}, },
"enterPasswordConfirm": { "enterPasswordConfirm": {
"message": "Inserisci la tua password per confermare" "message": "Inserisci la tua password per confermare"
}, },
"enterPasswordContinue": {
"message": "Inserisci la tua password per continuare"
},
"etherscanView": { "etherscanView": {
"message": "Vedi account su Etherscan" "message": "Vedi account su Etherscan"
}, },
"exchangeRate": { "exchangeRate": {
"message": "Tasso di cambio" "message": "Tasso di cambio"
}, },
"expandView": {
"message": "Expand View"
},
"exportPrivateKey": { "exportPrivateKey": {
"message": "Esporta Chiave Privata" "message": "Esporta Chiave Privata"
}, },
@ -257,7 +377,7 @@
"message": "Esporta chiave privata a tuo rischio." "message": "Esporta chiave privata a tuo rischio."
}, },
"failed": { "failed": {
"message": "Fallito" "message": "Fallita"
}, },
"fiat": { "fiat": {
"message": "FIAT", "message": "FIAT",
@ -270,6 +390,9 @@
"followTwitter": { "followTwitter": {
"message": "Seguici su Twitter" "message": "Seguici su Twitter"
}, },
"forgetDevice": {
"message": "Dimentica questo dispositivo"
},
"from": { "from": {
"message": "Da" "message": "Da"
}, },
@ -279,6 +402,9 @@
"fromShapeShift": { "fromShapeShift": {
"message": "Da ShapeShift" "message": "Da ShapeShift"
}, },
"functionType": {
"message": "Tipo della Funzione"
},
"gas": { "gas": {
"message": "Gas", "message": "Gas",
"description": "Piccola indicazione del costo del gas" "description": "Piccola indicazione del costo del gas"
@ -310,6 +436,9 @@
"gasPriceRequired": { "gasPriceRequired": {
"message": "Prezzo Gas Richiesto" "message": "Prezzo Gas Richiesto"
}, },
"generatingTransaction": {
"message": "Generando la transazione"
},
"getEther": { "getEther": {
"message": "Ottieni Ether" "message": "Ottieni Ether"
}, },
@ -317,10 +446,28 @@
"message": "Ottieni Get Ether da un faucet per $1", "message": "Ottieni Get Ether da un faucet per $1",
"description": "Visualizza il nome della rete per il faucet Ether" "description": "Visualizza il nome della rete per il faucet Ether"
}, },
"getHelp": {
"message": "Aiuto."
},
"greaterThanMin": { "greaterThanMin": {
"message": "deve essere maggiore o uguale a $1.", "message": "deve essere maggiore o uguale a $1.",
"description": "aiuto per inserire un input esadecimale come decimale" "description": "aiuto per inserire un input esadecimale come decimale"
}, },
"hardware": {
"message": "hardware"
},
"hardwareWalletConnected": {
"message": "Portafoglio hardware connesso"
},
"hardwareWallets": {
"message": "Connetti portafoglio hardware"
},
"hardwareWalletsMsg": {
"message": "Selezione un portafoglio hardware che vuoi utilizzare con MetaMask"
},
"havingTroubleConnecting": {
"message": "Problemi di connessione?"
},
"here": { "here": {
"message": "qui", "message": "qui",
"description": "per intendere -clicca qui- per maggiori informazioni (va con troubleTokenBalances)" "description": "per intendere -clicca qui- per maggiori informazioni (va con troubleTokenBalances)"
@ -328,6 +475,9 @@
"hereList": { "hereList": {
"message": "Questa è una lista!!!!" "message": "Questa è una lista!!!!"
}, },
"hexData": {
"message": "Dati Hex"
},
"hide": { "hide": {
"message": "Nascondi" "message": "Nascondi"
}, },
@ -337,6 +487,9 @@
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "Nascondi Token?" "message": "Nascondi Token?"
}, },
"history": {
"message": "Storico"
},
"howToDeposit": { "howToDeposit": {
"message": "Come vuoi depositare Ether?" "message": "Come vuoi depositare Ether?"
}, },
@ -363,9 +516,18 @@
"message": "Importato", "message": "Importato",
"description": "stato che conferma che un account è stato totalmente caricato nel portachiavi" "description": "stato che conferma che un account è stato totalmente caricato nel portachiavi"
}, },
"importUsingSeed": {
"message": "Importa account con frase seed"
},
"info": {
"message": "Informazioni"
},
"infoHelp": { "infoHelp": {
"message": "Informazioni & Aiuto" "message": "Informazioni & Aiuto"
}, },
"initialTransactionConfirmed": {
"message": "La transazione iniziale è stata confermata dalla rete. Clicca OK per tornare indietro."
},
"insufficientFunds": { "insufficientFunds": {
"message": "Fondi non sufficienti." "message": "Fondi non sufficienti."
}, },
@ -390,6 +552,9 @@
"invalidRPC": { "invalidRPC": {
"message": "URI RPC invalido" "message": "URI RPC invalido"
}, },
"invalidSeedPhrase": {
"message": "Frase seed non valida"
},
"jsonFail": { "jsonFail": {
"message": "Qualcosa è andato storto. Assicurati che il file JSON sia formattato correttamente." "message": "Qualcosa è andato storto. Assicurati che il file JSON sia formattato correttamente."
}, },
@ -397,12 +562,24 @@
"message": "File JSON", "message": "File JSON",
"description": "formato per importare un account" "description": "formato per importare un account"
}, },
"keepTrackTokens": {
"message": "Tieni traccia dei tokens che hai acquistato con il tuo account MetaMask."
},
"kovan": { "kovan": {
"message": "Rete di test Kovan" "message": "Rete di test Kovan"
}, },
"knowledgeDataBase": { "knowledgeDataBase": {
"message": "Visita la nostra Knowledge Base" "message": "Visita la nostra Knowledge Base"
}, },
"max": {
"message": "Massimo"
},
"learnMore": {
"message": "Scopri di più"
},
"ledgerAccountRestriction": {
"message": "E' necessario utilizzare l'ultimo account prima di poterne aggiungere uno nuovo."
},
"lessThanMax": { "lessThanMax": {
"message": "deve essere minore o uguale a $1.", "message": "deve essere minore o uguale a $1.",
"description": "aiuto per inserire un input esadecimale come decimale" "description": "aiuto per inserire un input esadecimale come decimale"
@ -410,6 +587,9 @@
"likeToAddTokens": { "likeToAddTokens": {
"message": "Vorresti aggiungere questi token?" "message": "Vorresti aggiungere questi token?"
}, },
"links": {
"message": "Collegamenti"
},
"limit": { "limit": {
"message": "Limite" "message": "Limite"
}, },
@ -437,17 +617,26 @@
"mainnet": { "mainnet": {
"message": "Rete Ethereum Principale" "message": "Rete Ethereum Principale"
}, },
"menu": {
"message": "Menu"
},
"message": { "message": {
"message": "Messaggio" "message": "Messaggio"
}, },
"metamaskDescription": { "metamaskDescription": {
"message": "MetaMask è una cassaforte sicura per identità su Ethereum." "message": "MetaMask è una cassaforte sicura per identità su Ethereum."
}, },
"metamaskSeedWords": {
"message": "Parole Seed di MetaMask"
},
"metamaskVersion": {
"message": "versione di MetaMask"
},
"min": { "min": {
"message": "Minimo" "message": "Minimo"
}, },
"myAccounts": { "myAccounts": {
"message": "Account Miei" "message": "Miei Account"
}, },
"mustSelectOne": { "mustSelectOne": {
"message": "Devi selezionare almeno un token." "message": "Devi selezionare almeno un token."
@ -469,6 +658,9 @@
"networks": { "networks": {
"message": "Reti" "message": "Reti"
}, },
"nevermind": {
"message": "Non importa"
},
"newAccount": { "newAccount": {
"message": "Nuovo Account" "message": "Nuovo Account"
}, },
@ -482,6 +674,9 @@
"newPassword": { "newPassword": {
"message": "Nuova Password (minimo 8 caratteri)" "message": "Nuova Password (minimo 8 caratteri)"
}, },
"newPassword8Chars": {
"message": "Nuova Password (minimo 8 caratteri)"
},
"newRecipient": { "newRecipient": {
"message": "Nuovo Destinatario" "message": "Nuovo Destinatario"
}, },
@ -489,7 +684,7 @@
"message": "Nuovo URL RPC" "message": "Nuovo URL RPC"
}, },
"next": { "next": {
"message": "Prossimo" "message": "Avanti"
}, },
"noAddressForName": { "noAddressForName": {
"message": "Nessun indirizzo è stato impostato per questo nome." "message": "Nessun indirizzo è stato impostato per questo nome."
@ -497,32 +692,75 @@
"noDeposits": { "noDeposits": {
"message": "Nessun deposito ricevuto" "message": "Nessun deposito ricevuto"
}, },
"noConversionRateAvailable": {
"message": "Tasso di Conversione non Disponibile"
},
"noTransactionHistory": { "noTransactionHistory": {
"message": "Nessuna cronologia delle transazioni." "message": "Nessuna cronologia delle transazioni."
}, },
"noTransactions": { "noTransactions": {
"message": "Nessuna Transazione" "message": "Nessuna Transazione"
}, },
"notFound": {
"message": "Non Trovata"
},
"notStarted": { "notStarted": {
"message": "Non Iniziato" "message": "Non Iniziato"
}, },
"noWebcamFoundTitle": {
"message": "Webcam non trovata"
},
"noWebcamFound": {
"message": "La webcam del tuo computer non è stata trovata. Per favore riprovaci."
},
"oldUI": { "oldUI": {
"message": "Vecchia interfaccia" "message": "Vecchia interfaccia"
}, },
"oldUIMessage": { "oldUIMessage": {
"message": "Sei ritornato alla vecchia interfaccia. Puoi ritornare alla nuova interfaccia tramite l'opzione nel menu a discesa in alto a destra." "message": "Sei ritornato alla vecchia interfaccia. Puoi ritornare alla nuova interfaccia tramite l'opzione nel menu a discesa in alto a destra."
}, },
"onlySendToEtherAddress": {
"message": "Invia ETH solamente ad un account Ethereum."
},
"onlySendTokensToAccountAddress": {
"message": "Invia solamente $1 ad un account Ethereum.",
"description": "mostra il simbolo del token"
},
"openInTab": {
"message": "Apri in una scheda"
},
"or": { "or": {
"message": "o", "message": "o",
"description": "scelta tra creare o importare un nuovo account" "description": "scelta tra creare o importare un nuovo account"
}, },
"orderOneHere": {
"message": "Compra un Trezor o un Ledger e tieni i tuoi soldi al sicuro"
},
"origin": {
"message": "Origine"
},
"outgoing": {
"message": "In Uscita"
},
"parameters": {
"message": "Parametri"
},
"password": {
"message": "Password"
},
"passwordCorrect": { "passwordCorrect": {
"message": "Assicurati che la password sia corretta." "message": "Assicurati che la password sia corretta."
}, },
"passwordsDontMatch": {
"message": "Le Password Non Corrispondonos"
},
"passwordMismatch": { "passwordMismatch": {
"message": "le password non corrispondono", "message": "le password non corrispondono",
"description": "nella creazione della password, le due password all'interno dei campi non corrispondono" "description": "nella creazione della password, le due password all'interno dei campi non corrispondono"
}, },
"passwordNotLongEnough": {
"message": "Password non abbastanza lunga"
},
"passwordShort": { "passwordShort": {
"message": "password non sufficientemente lunga", "message": "password non sufficientemente lunga",
"description": "nella creazione della password, la password non è lunga abbastanza" "description": "nella creazione della password, la password non è lunga abbastanza"
@ -534,12 +772,21 @@
"pasteSeed": { "pasteSeed": {
"message": "Incolla la tua frase seed qui!" "message": "Incolla la tua frase seed qui!"
}, },
"pending": {
"message": "in corso"
},
"personalAddressDetected": { "personalAddressDetected": {
"message": "Rilevato indirizzo personale. Inserisci l'indirizzo del contratto del token." "message": "Rilevato indirizzo personale. Inserisci l'indirizzo del contratto del token."
}, },
"pleaseReviewTransaction": { "pleaseReviewTransaction": {
"message": "Ricontrolla la tua transazione." "message": "Ricontrolla la tua transazione."
}, },
"popularTokens": {
"message": "Tokens Popolari"
},
"prev": {
"message": "Precedente"
},
"privacyMsg": { "privacyMsg": {
"message": "Politica sulla Privacy" "message": "Politica sulla Privacy"
}, },
@ -556,6 +803,9 @@
"qrCode": { "qrCode": {
"message": "Mostra Codice QR" "message": "Mostra Codice QR"
}, },
"queue": {
"message": "Coda"
},
"readdToken": { "readdToken": {
"message": "Puoi aggiungere nuovamente questo token in futuro andando in “Aggiungi token” nel menu delle opzioni del tuo account." "message": "Puoi aggiungere nuovamente questo token in futuro andando in “Aggiungi token” nel menu delle opzioni del tuo account."
}, },
@ -574,36 +824,87 @@
"refundAddress": { "refundAddress": {
"message": "Indirizzo di Rimborso" "message": "Indirizzo di Rimborso"
}, },
"reject": {
"message": "Reject"
},
"rejectAll": {
"message": "Reject All"
},
"rejectTxsN": {
"message": "Reject $1 transactions"
},
"rejectTxsDescription": {
"message": "You are about to batch reject $1 transactions."
},
"rejected": { "rejected": {
"message": "Respinta" "message": "Respinta"
}, },
"reset": {
"message": "Reset"
},
"resetAccount": { "resetAccount": {
"message": "Resetta Account" "message": "Resetta Account"
}, },
"resetAccountDescription": {
"message": "Resettare il tuo account cancellerà lo storico delle transazioni."
},
"restoreFromSeed": { "restoreFromSeed": {
"message": "Ripristina da una frase seed" "message": "Ripristina da una frase seed"
}, },
"restoreVault": {
"message": "Ripristina Cassaforte"
},
"restoreAccountWithSeed": {
"message": "Ripristina Account con la Frase Seed"
},
"required": { "required": {
"message": "Richiesto" "message": "Richiesto"
}, },
"retryWithMoreGas": { "retryWithMoreGas": {
"message": "Riprova con un prezzo del Gas maggiore qui" "message": "Riprova con un prezzo del Gas maggiore qui"
}, },
"restore": {
"message": "Ripristina"
},
"revealSeedWords": { "revealSeedWords": {
"message": "Rivela Frase Seed" "message": "Rivela Frase Seed"
}, },
"revealSeedWordsTitle": {
"message": "Frase Seed"
},
"revealSeedWordsDescription": {
"message": "Se cambierai browser o computer, ti servirà questa frase seed per accedere ai tuoi account. Salvala in un posto sicuro e segreto."
},
"revealSeedWordsWarningTitle": {
"message": "NON CONDIVIDERE questa frase con nessuno!"
},
"revealSeedWordsWarning": { "revealSeedWordsWarning": {
"message": "Non ripristinare la tua frase seed in pubblico!. Queste parole possono essere usate per rubare il tuo account." "message": "Non ripristinare la tua frase seed in pubblico!. Queste parole possono essere usate per rubare il tuo account."
}, },
"revert": { "revert": {
"message": "Annulla" "message": "Annulla"
}, },
"remove": {
"message": "rimuovi"
},
"removeAccount": {
"message": "Rimuovi account"
},
"removeAccountDescription": {
"message": "Questo account sarà rimosso dal tuo portafoglio. Per favore assicurati che hai la frase seed originale o la chiave privata per questo account importato prima di continuare. Puoi nuovamente importare o creare un account dal menù a tendina. "
},
"readyToConnect": {
"message": "Pronto a Connetterti?"
},
"rinkeby": { "rinkeby": {
"message": "Rete di test Rinkeby" "message": "Rete di test Rinkeby"
}, },
"ropsten": { "ropsten": {
"message": "Rete di test Ropsten" "message": "Rete di test Ropsten"
}, },
"rpc": {
"message": "RPC Personalizzata"
},
"sampleAccountName": { "sampleAccountName": {
"message": "Es: Il mio nuovo account", "message": "Es: Il mio nuovo account",
"description": "Aiuta l'utente a capire il concetto di aggiungere un nome leggibile al loro account" "description": "Aiuta l'utente a capire il concetto di aggiungere un nome leggibile al loro account"
@ -611,6 +912,9 @@
"save": { "save": {
"message": "Salva" "message": "Salva"
}, },
"saveAsCsvFile": {
"message": "Salva Come File CSV"
},
"saveAsFile": { "saveAsFile": {
"message": "Salva come File", "message": "Salva come File",
"description": "Processo per esportare un account" "description": "Processo per esportare un account"
@ -618,9 +922,18 @@
"saveSeedAsFile": { "saveSeedAsFile": {
"message": "Salva la Frase Seed come File" "message": "Salva la Frase Seed come File"
}, },
"scanInstructions": {
"message": "Posizione il codice QR davanti alla fotocamera"
},
"scanQrCode": {
"message": "Scansiona Codice QR"
},
"search": { "search": {
"message": "Cerca" "message": "Cerca"
}, },
"searchResults": {
"message": "Risultati Ricerca"
},
"secretPhrase": { "secretPhrase": {
"message": "Inserisci la tua frase segreta di dodici parole per ripristinare la cassaforte." "message": "Inserisci la tua frase segreta di dodici parole per ripristinare la cassaforte."
}, },
@ -633,6 +946,9 @@
"selectCurrency": { "selectCurrency": {
"message": "Seleziona Moneta" "message": "Seleziona Moneta"
}, },
"selectLocale": {
"message": "Selezione Lingua"
},
"selectService": { "selectService": {
"message": "Seleziona Servizio" "message": "Seleziona Servizio"
}, },
@ -648,6 +964,33 @@
"sendTokens": { "sendTokens": {
"message": "Invia Tokens" "message": "Invia Tokens"
}, },
"sentEther": {
"message": "ether inviati"
},
"sentTokens": {
"message": "tokens inviati"
},
"separateEachWord": {
"message": "Separa ogni parola con un solo spazio"
},
"searchTokens": {
"message": "Cerca Tokens"
},
"selectAnAddress": {
"message": "Seleziona un Indirizzo"
},
"selectAnAccount": {
"message": "Seleziona un Account"
},
"selectAnAccountHelp": {
"message": "Selezione l'account da visualizzare in MetaMask"
},
"selectHdPath": {
"message": "Seleziona Percorso HD"
},
"selectPathHelp": {
"message": "Se non vedi il tuo account Ledger esistente di seguito, prova a cambiare il percorso in \"Legacy (MEW / MyCrypto)\""
},
"sendTokensAnywhere": { "sendTokensAnywhere": {
"message": "Invia Tokens a chiunque abbia un account Ethereum" "message": "Invia Tokens a chiunque abbia un account Ethereum"
}, },
@ -663,9 +1006,21 @@
"showQRCode": { "showQRCode": {
"message": "Mostra Codie QR" "message": "Mostra Codie QR"
}, },
"showHexData": {
"message": "Mostra Dati Hex"
},
"showHexDataDescription": {
"message": "Seleziona per mostrare il campo dei dati hex nella schermata di invio"
},
"sign": { "sign": {
"message": "Firma" "message": "Firma"
}, },
"signatureRequest": {
"message": "Firma Richiesta"
},
"signed": {
"message": "Firmata"
},
"signMessage": { "signMessage": {
"message": "Firma Messaggio" "message": "Firma Messaggio"
}, },
@ -681,6 +1036,15 @@
"spaceBetween": { "spaceBetween": {
"message": "ci può essere solo uno spazio tra le parole" "message": "ci può essere solo uno spazio tra le parole"
}, },
"speedUp": {
"message": "velocizza"
},
"speedUpTitle": {
"message": "Velocizza Transazione"
},
"speedUpSubtitle": {
"message": "Aumenta il prezzo del gas per tentare di sovrascrivere e velocizzare la transazione"
},
"status": { "status": {
"message": "Stato" "message": "Stato"
}, },
@ -690,9 +1054,33 @@
"stateLogsDescription": { "stateLogsDescription": {
"message": "I log di stato contengono i tuoi indirizzi pubblici e le transazioni effettuate." "message": "I log di stato contengono i tuoi indirizzi pubblici e le transazioni effettuate."
}, },
"stateLogError": {
"message": "Errore nel recupero dei log di stato."
},
"step1HardwareWallet": {
"message": "1. Connetti Portafoglio Hardware"
},
"step1HardwareWalletMsg": {
"message": "Connetti il tuo portafoglio hardware al tuo computer."
},
"step2HardwareWallet": {
"message": "2. Seleziona un Account"
},
"step2HardwareWalletMsg": {
"message": "Selezione l'account che vuoi vedere. Puoi selezionarne solo uno alla volta."
},
"step3HardwareWallet": {
"message": "3. Inizia a usare dApps e molto altro ancora!"
},
"step3HardwareWalletMsg": {
"message": "Usa il tuo account hardware come utilizzeresti qualsiasi account Ethereum. Accedi alle dApps, invia Eth, compra e conserva token ERC20 e token non fungibili come CryptoKitties"
},
"submit": { "submit": {
"message": "Invia" "message": "Invia"
}, },
"submitted": {
"message": "Inviata"
},
"supportCenter": { "supportCenter": {
"message": "Visita il nostro Centro di Supporto" "message": "Visita il nostro Centro di Supporto"
}, },
@ -715,6 +1103,9 @@
"message": "$1 a ETH via ShapeShift", "message": "$1 a ETH via ShapeShift",
"description": "il sistema riempirà il tipo di deposito all'inizio del messaggio" "description": "il sistema riempirà il tipo di deposito all'inizio del messaggio"
}, },
"token": {
"message": "Token"
},
"tokenAddress": { "tokenAddress": {
"message": "Indirizzo Token" "message": "Indirizzo Token"
}, },
@ -736,22 +1127,61 @@
"total": { "total": {
"message": "Totale" "message": "Totale"
}, },
"transaction": {
"message": "transazione"
},
"transactionConfirmed": {
"message": "Transazione confermata il $2."
},
"transactionCreated": {
"message": "Transazione di valore $1 creata il $2."
},
"transactionWithNonce": {
"message": "Transazione $1"
},
"transactionDropped": {
"message": "Transazione abbandonata il $2."
},
"transactionSubmitted": {
"message": "Transazione inviata il $2."
},
"transactionUpdated": {
"message": "Transazione aggiornata il $2."
},
"transactionUpdatedGas": {
"message": "Transazione aggiornata con un prezzo del gas di $1 il $2."
},
"transactions": { "transactions": {
"message": "transazioni" "message": "transazioni"
}, },
"transactionError": {
"message": "Errore Transazione. Eccceziona generata nel codice del contratto."
},
"transactionMemo": { "transactionMemo": {
"message": "Promemoria Transazione (opzionale)" "message": "Promemoria Transazione (opzionale)"
}, },
"transactionNumber": { "transactionNumber": {
"message": "Numero Transazione" "message": "Numero Transazione"
}, },
"transfer": {
"message": "Trasferisci"
},
"transferFrom": {
"message": "Transfer From"
},
"transfers": { "transfers": {
"message": "Trasferimenti" "message": "Trasferimenti"
}, },
"trezorHardwareWallet": {
"message": "TREZOR Portafoglio Hardware"
},
"troubleTokenBalances": { "troubleTokenBalances": {
"message": "Abbiamo avuto un problema a caricare il bilancio dei tuoi token. Puoi vederlo ", "message": "Abbiamo avuto un problema a caricare il bilancio dei tuoi token. Puoi vederlo ",
"description": "Seguito da un link (qui) per vedere il bilancio dei token" "description": "Seguito da un link (qui) per vedere il bilancio dei token"
}, },
"tryAgain": {
"message": "Prova di nuovo"
},
"twelveWords": { "twelveWords": {
"message": "Queste 12 parole sono l'unico modo per ripristinare i tuoi account MetaMask. \nSalvale in un posto sicuro e segreto." "message": "Queste 12 parole sono l'unico modo per ripristinare i tuoi account MetaMask. \nSalvale in un posto sicuro e segreto."
}, },
@ -764,18 +1194,45 @@
"uiWelcomeMessage": { "uiWelcomeMessage": {
"message": "Stai utilizzanto la nuova interfaccia di MetaMask. Guarda in giro, prova nuove funzionalità come inviare token, e facci sapere se hai dei problemi." "message": "Stai utilizzanto la nuova interfaccia di MetaMask. Guarda in giro, prova nuove funzionalità come inviare token, e facci sapere se hai dei problemi."
}, },
"unapproved": {
"message": "Non approvata"
},
"unavailable": { "unavailable": {
"message": "Non Disponibile" "message": "Non Disponibile"
}, },
"units": {
"message": "unità"
},
"unknown": { "unknown": {
"message": "Sconosciuto" "message": "Sconosciuto"
}, },
"unknownFunction": {
"message": "Funzione Sconosciuta"
},
"unknownNetwork": { "unknownNetwork": {
"message": "Rete Privata Sconosciuta" "message": "Rete Privata Sconosciuta"
}, },
"unknownNetworkId": { "unknownNetworkId": {
"message": "ID rete sconosciuto" "message": "ID rete sconosciuto"
}, },
"unknownQrCode": {
"message": "Errore: Non siamo riusciti a identificare il codice QR"
},
"unknownCameraErrorTitle": {
"message": "Ooops! Qualcosa è andato storto...."
},
"unknownCameraError": {
"message": "C'è stato un errore nel tentativo di accedere alla fotocamera. Per favore prova di nuovo..."
},
"unlock": {
"message": "Sblocca"
},
"unlockMessage": {
"message": "Il web decentralizzato ti attende"
},
"updatedWithDate": {
"message": "Aggiornata $1"
},
"uriErrorMsg": { "uriErrorMsg": {
"message": "Gli URI richiedono un prefisso HTTP/HTTPS." "message": "Gli URI richiedono un prefisso HTTP/HTTPS."
}, },
@ -798,22 +1255,40 @@
"viewAccount": { "viewAccount": {
"message": "Vedi Account" "message": "Vedi Account"
}, },
"viewOnEtherscan": {
"message": "Vedi su Etherscan"
},
"visitWebSite": { "visitWebSite": {
"message": "Visita il nostro sito web" "message": "Visita il nostro sito web"
}, },
"walletSeed": {
"message": "Seed del Portafoglio"
},
"warning": { "warning": {
"message": "Attenzione" "message": "Attenzione"
}, },
"welcomeBack": {
"message": "Bentornato!"
},
"welcomeBeta": { "welcomeBeta": {
"message": "Benvenuto nella Beta di MetaMask" "message": "Benvenuto nella Beta di MetaMask"
}, },
"whatsThis": { "whatsThis": {
"message": "Cos'è questo?" "message": "Cos'è questo?"
}, },
"yesLetsTry": {
"message": "Si, proviamo"
},
"youNeedToAllowCameraAccess": {
"message": "Devi consentire l'accesso alla fotocamera per usare questa funzionalità."
},
"yourSigRequested": { "yourSigRequested": {
"message": "E' richiesta la tua firma" "message": "E' richiesta la tua firma"
}, },
"youSign": { "youSign": {
"message": "Ti stai connettendo" "message": "Ti stai connettendo"
},
"yourPrivateSeedPhrase": {
"message": "La tua frase seed privata"
} }
} }

View File

@ -2,6 +2,9 @@
"accept": { "accept": {
"message": "수락" "message": "수락"
}, },
"accessingYourCamera": {
"message": "카메라에 접근 중..."
},
"account": { "account": {
"message": "계정" "message": "계정"
}, },
@ -11,6 +14,15 @@
"accountName": { "accountName": {
"message": "계정 이름" "message": "계정 이름"
}, },
"accountOptions": {
"message": "계정 옵션"
},
"accountSelectionRequired": {
"message": "계정을 선택하셔야 합니다!"
},
"activityLog": {
"message": "활동 로그"
},
"address": { "address": {
"message": "주소" "message": "주소"
}, },
@ -23,6 +35,9 @@
"addTokens": { "addTokens": {
"message": "토큰 추가" "message": "토큰 추가"
}, },
"addSuggestedTokens": {
"message": "제안된 토큰 추가"
},
"addAcquiredTokens": { "addAcquiredTokens": {
"message": "메타마스크를 통해 획득한 토큰 추가" "message": "메타마스크를 통해 획득한 토큰 추가"
}, },
@ -40,12 +55,18 @@
"message": "메타마스크", "message": "메타마스크",
"description": "애플리케이션 이름" "description": "애플리케이션 이름"
}, },
"approve": {
"message": "수락"
},
"approved": { "approved": {
"message": "수락" "message": "수락"
}, },
"attemptingConnect": { "attemptingConnect": {
"message": "블록체인에 접속을 시도하는 중입니다." "message": "블록체인에 접속을 시도하는 중입니다."
}, },
"attemptToCancel": {
"message": "취소 하시겠습니까?"
},
"attributions": { "attributions": {
"message": "속성" "message": "속성"
}, },
@ -75,7 +96,10 @@
"message": "Blockies 아이덴티콘 사용" "message": "Blockies 아이덴티콘 사용"
}, },
"borrowDharma": { "borrowDharma": {
"message": "Dharma에서 대여하기(Beta)" "message": "Dharma에서 대출받기 (Beta)"
},
"browserNotSupported": {
"message": "브라우저를 지원하지 않습니다..."
}, },
"builtInCalifornia": { "builtInCalifornia": {
"message": "메타마스크는 캘리포니아에서 디자인되고 만들어졌습니다." "message": "메타마스크는 캘리포니아에서 디자인되고 만들어졌습니다."
@ -89,12 +113,24 @@
"buyCoinbaseExplainer": { "buyCoinbaseExplainer": {
"message": "코인베이스는 비트코인, 이더리움, 라이트코인을 거래할 수 있는 유명한 거래소입니다." "message": "코인베이스는 비트코인, 이더리움, 라이트코인을 거래할 수 있는 유명한 거래소입니다."
}, },
"bytes": {
"message": "바이트"
},
"ok": { "ok": {
"message": "확인" "message": "확인"
}, },
"cancel": { "cancel": {
"message": "취소" "message": "취소"
}, },
"cancelAttempt": {
"message": "취소 시도"
},
"cancellationGasFee": {
"message": "취소 가스 수수료"
},
"cancelN": {
"message": "모든 $1 트랜잭션 취소"
},
"classicInterface": { "classicInterface": {
"message": "예전 인터페이스" "message": "예전 인터페이스"
}, },
@ -104,6 +140,9 @@
"close": { "close": {
"message": "닫기" "message": "닫기"
}, },
"chromeRequiredForHardwareWallets": {
"message": "하드웨어 지갑을 연결하기 위해서는 구글 크롬에서 메타마스크를 사용하셔야 합니다."
},
"confirm": { "confirm": {
"message": "승인" "message": "승인"
}, },
@ -119,6 +158,36 @@
"confirmTransaction": { "confirmTransaction": {
"message": "트랜잭션 승인" "message": "트랜잭션 승인"
}, },
"connectHardwareWallet": {
"message": "하드웨어 지갑 연결"
},
"connect": {
"message": "연결"
},
"connecting": {
"message": "연결 중..."
},
"connectingToMainnet": {
"message": "이더리움 메인넷 접속 중"
},
"connectingToRopsten": {
"message": "Ropsten 테스트넷 접속 중"
},
"connectingToKovan": {
"message": "Kovan 테스트넷 접속 중"
},
"connectingToRinkeby": {
"message": "Rinkeby 테스트넷 접속 중"
},
"connectingToUnknown": {
"message": "알 수 없는 네트워크 접속 중"
},
"connectToLedger": {
"message": "Ledger 연결"
},
"connectToTrezor": {
"message": "Trezor 연결"
},
"continue": { "continue": {
"message": "계속" "message": "계속"
}, },
@ -146,6 +215,9 @@
"copy": { "copy": {
"message": "복사" "message": "복사"
}, },
"copyAddress": {
"message": "클립보드로 주소 복사"
},
"copyToClipboard": { "copyToClipboard": {
"message": "클립보드로 복사" "message": "클립보드로 복사"
}, },
@ -169,11 +241,17 @@
"description": "거래 유형 (암호화폐)" "description": "거래 유형 (암호화폐)"
}, },
"currentConversion": { "currentConversion": {
"message": "선택된 단위" "message": "현재 통화"
},
"currentLanguage": {
"message": "현재 언어"
}, },
"currentNetwork": { "currentNetwork": {
"message": "현재 네트워크" "message": "현재 네트워크"
}, },
"currentRpc": {
"message": "현재 RPC"
},
"customGas": { "customGas": {
"message": "가스 설정" "message": "가스 설정"
}, },
@ -241,9 +319,15 @@
"done": { "done": {
"message": "완료" "message": "완료"
}, },
"downloadGoogleChrome": {
"message": "구글 크롬 다운로드"
},
"downloadStateLogs": { "downloadStateLogs": {
"message": "상태 로그 다운로드" "message": "상태 로그 다운로드"
}, },
"dontHaveAHardwareWallet": {
"message": "하드웨어 지갑이 없나요?"
},
"dropped": { "dropped": {
"message": "중단됨" "message": "중단됨"
}, },
@ -254,7 +338,7 @@
"message": "계정 이름 수정" "message": "계정 이름 수정"
}, },
"editingTransaction": { "editingTransaction": {
"message": "트랜션을 변경합니다" "message": "트랜션을 변경합니다"
}, },
"emailUs": { "emailUs": {
"message": "저자에게 메일 보내기!" "message": "저자에게 메일 보내기!"
@ -262,6 +346,9 @@
"encryptNewDen": { "encryptNewDen": {
"message": "새로운 DEN을 암호화" "message": "새로운 DEN을 암호화"
}, },
"ensNameNotFound": {
"message": "ENS 이름을 찾을 수 없습니다"
},
"enterPassword": { "enterPassword": {
"message": "비밀번호를 입력해주세요" "message": "비밀번호를 입력해주세요"
}, },
@ -271,18 +358,15 @@
"enterPasswordContinue": { "enterPasswordContinue": {
"message": "계속하기 위해 비밀번호 입력" "message": "계속하기 위해 비밀번호 입력"
}, },
"passwordNotLongEnough": {
"message": "비밀번호가 충분히 길지 않습니다"
},
"passwordsDontMatch": {
"message": "비밀번호가 맞지 않습니다"
},
"etherscanView": { "etherscanView": {
"message": "이더스캔에서 계정보기" "message": "이더스캔에서 계정보기"
}, },
"exchangeRate": { "exchangeRate": {
"message": "환율" "message": "환율"
}, },
"expandView": {
"message": "큰 화면으로 보기"
},
"exportPrivateKey": { "exportPrivateKey": {
"message": "개인키 내보내기" "message": "개인키 내보내기"
}, },
@ -303,15 +387,21 @@
"followTwitter": { "followTwitter": {
"message": "트위터에서 팔로우하세요" "message": "트위터에서 팔로우하세요"
}, },
"forgetDevice": {
"message": "장치 연결 해제"
},
"from": { "from": {
"message": "보내는 이" "message": "보내는 이"
}, },
"fromToSame": { "fromToSame": {
"message": "보내고 받는 주소는 동일할 수 없습니다" "message": "보내고 받는 주소는 같을 수 없습니다"
}, },
"fromShapeShift": { "fromShapeShift": {
"message": "ShapeShift로부터" "message": "ShapeShift로부터"
}, },
"functionType": {
"message": "함수 유형"
},
"gas": { "gas": {
"message": "가스", "message": "가스",
"description": "가스 가격의 줄임" "description": "가스 가격의 줄임"
@ -329,7 +419,7 @@
"message": "가스 한도가 필요합니다." "message": "가스 한도가 필요합니다."
}, },
"gasLimitTooLow": { "gasLimitTooLow": {
"message": "가스 한도는 최소 21000 이상이야 합니다." "message": "가스 한도는 최소 21000 이상이야 합니다."
}, },
"generatingSeed": { "generatingSeed": {
"message": "시드 생성 중..." "message": "시드 생성 중..."
@ -353,10 +443,28 @@
"message": "파우셋에서 $1에 달하는 이더를 얻으세요.", "message": "파우셋에서 $1에 달하는 이더를 얻으세요.",
"description": "이더 파우셋에 대한 네트워크 이름을 표시합니다" "description": "이더 파우셋에 대한 네트워크 이름을 표시합니다"
}, },
"getHelp": {
"message": "도움말"
},
"greaterThanMin": { "greaterThanMin": {
"message": "$1 이상이어야 합니다.", "message": "$1 이상이어야 합니다.",
"description": "10진수 입력으로 hex값 입력을 도와줍니다" "description": "10진수 입력으로 hex값 입력을 도와줍니다"
}, },
"hardware": {
"message": "하드웨어"
},
"hardwareWalletConnected": {
"message": "하드웨어 지갑이 연결됨"
},
"hardwareWallets": {
"message": "하드웨어 지갑 연결"
},
"hardwareWalletsMsg": {
"message": "메타마스크에서 사용할 하드웨어 지갑을 선택해주세요"
},
"havingTroubleConnecting": {
"message": "연결에 문제가 있나요?"
},
"here": { "here": {
"message": "여기", "message": "여기",
"description": "as in -click here- for more information (goes with troubleTokenBalances)" "description": "as in -click here- for more information (goes with troubleTokenBalances)"
@ -364,6 +472,9 @@
"hereList": { "hereList": {
"message": "리스트가 있습니다!!!!" "message": "리스트가 있습니다!!!!"
}, },
"hexData": {
"message": "Hex 데이터"
},
"hide": { "hide": {
"message": "숨기기" "message": "숨기기"
}, },
@ -373,6 +484,9 @@
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "토큰 숨기기?" "message": "토큰 숨기기?"
}, },
"history": {
"message": "히스토리"
},
"howToDeposit": { "howToDeposit": {
"message": "어떤 방법으로 이더를 입금하시겠습니까?" "message": "어떤 방법으로 이더를 입금하시겠습니까?"
}, },
@ -397,16 +511,19 @@
}, },
"imported": { "imported": {
"message": "가져온 계정", "message": "가져온 계정",
"description": "이 상태는 해당 계정이 keyring으로 완전히 적재된 상태임을 표시합니다" "description": "이 상태는 해당 계정이 keyring으로 완전히 로드된 상태임을 표시합니다"
}, },
"importUsingSeed": { "importUsingSeed": {
"message": "계정 시드 구문으로 가져오기" "message": "계정 시드 구문으로 가져오기"
}, },
"info": {
"message": "정보"
},
"infoHelp": { "infoHelp": {
"message": "정보 및 도움말" "message": "정보 및 도움말"
}, },
"initialTransactionConfirmed": { "initialTransactionConfirmed": {
"message": "초기 트랜잭션이 네트워크를 통해 확정되었습니다. 확인을 누르 이전으로 돌아갑니다." "message": "초기 트랜잭션이 네트워크를 통해 확정되었습니다. 확인을 누르 이전으로 돌아갑니다."
}, },
"insufficientFunds": { "insufficientFunds": {
"message": "충분하지 않은 자금." "message": "충분하지 않은 자금."
@ -432,6 +549,9 @@
"invalidRPC": { "invalidRPC": {
"message": "올바르지 않은 RPC URI" "message": "올바르지 않은 RPC URI"
}, },
"invalidSeedPhrase": {
"message": "잘못된 시드 구문"
},
"jsonFail": { "jsonFail": {
"message": "이상이 있습니다. JSON 파일이 올바른 파일인지 확인해주세요." "message": "이상이 있습니다. JSON 파일이 올바른 파일인지 확인해주세요."
}, },
@ -452,7 +572,10 @@
"message": "최대" "message": "최대"
}, },
"learnMore": { "learnMore": {
"message": "더 배우기." "message": "더 알아보기."
},
"ledgerAccountRestriction": {
"message": "새 계정을 추가하려면 최소 마지막 계정을 사용해야 합니다."
}, },
"lessThanMax": { "lessThanMax": {
"message": "$1 이하여야합니다.", "message": "$1 이하여야합니다.",
@ -491,12 +614,18 @@
"mainnet": { "mainnet": {
"message": "이더리움 메인넷" "message": "이더리움 메인넷"
}, },
"menu": {
"message": "메뉴"
},
"message": { "message": {
"message": "메시지" "message": "메시지"
}, },
"metamaskDescription": { "metamaskDescription": {
"message": "메타마스크는 이더리움을 위한 안전한 신분 저장소입니다." "message": "메타마스크는 이더리움을 위한 안전한 신분 저장소입니다."
}, },
"metamaskVersion": {
"message": "메타마스크 버전"
},
"metamaskSeedWords": { "metamaskSeedWords": {
"message": "메타마스크 시드 단어" "message": "메타마스크 시드 단어"
}, },
@ -557,35 +686,72 @@
"noDeposits": { "noDeposits": {
"message": "입금 내역이 없습니다." "message": "입금 내역이 없습니다."
}, },
"noConversionRateAvailable": {
"message": "변환 비율을 찾을 수 없습니다"
},
"noTransactionHistory": { "noTransactionHistory": {
"message": "트랜잭션 기록이 없습니다." "message": "트랜잭션 기록이 없습니다."
}, },
"noTransactions": { "noTransactions": {
"message": "트랜잭션이 없습니다" "message": "트랜잭션이 없습니다"
}, },
"notFound": {
"message": "찾을 수 없음"
},
"notStarted": { "notStarted": {
"message": "시작 안 됨" "message": "시작 안 됨"
}, },
"noWebcamFound": {
"message": "컴퓨터의 웹캠을 찾을 수 없습니다. 다시 시도해보세요."
},
"noWebcamFoundTitle": {
"message": "웹캠이 없습니다"
},
"oldUI": { "oldUI": {
"message": "구버전 UI" "message": "구버전 UI"
}, },
"oldUIMessage": { "oldUIMessage": {
"message": "구버전 UI로 변경하셨습니다. 우 상단 드롭다운 메뉴에서 새 UI로 변경하실 수 있습니다." "message": "구버전 UI로 변경하셨습니다. 오른쪽 위 드롭다운 메뉴에서 새 UI로 변경하실 수 있습니다."
},
"onlySendToEtherAddress": {
"message": "이더리움 주소로 ETH만 송금하세요."
},
"onlySendTokensToAccountAddress": {
"message": "이더리움 계정 주소로 $1만 보내기.",
"description": "토큰 심볼 보이기"
},
"openInTab": {
"message": "탭으로 열기"
}, },
"or": { "or": {
"message": "또는", "message": "또는",
"description": "새 계정을 만들거나 가져오기 중에 선택하기" "description": "새 계정을 만들거나 가져오기 중에 선택하기"
}, },
"orderOneHere": {
"message": "Trezor 혹은 Ledger를 구입하고 자금을 콜드 스토리지에 저장합니다"
},
"origin": {
"message": "Origin"
},
"parameters": {
"message": "매개변수"
},
"password": { "password": {
"message": "비밀번호" "message": "비밀번호"
}, },
"passwordCorrect": { "passwordCorrect": {
"message": "비밀번호가 맞는지 확인해주세요." "message": "비밀번호가 맞는지 확인해주세요."
}, },
"passwordsDontMatch": {
"message": "비밀번호가 맞지 않습니다"
},
"passwordMismatch": { "passwordMismatch": {
"message": "비밀번호가 일치하지 않습니다.", "message": "비밀번호가 일치하지 않습니다.",
"description": "비밀번호를 생성하는 과정에서 두 개의 새 비밀번호 입력란이 일치하지 않습니다" "description": "비밀번호를 생성하는 과정에서 두 개의 새 비밀번호 입력란이 일치하지 않습니다"
}, },
"passwordNotLongEnough": {
"message": "비밀번호가 충분히 길지 않습니다"
},
"passwordShort": { "passwordShort": {
"message": "비밀번호가 짧습니다.", "message": "비밀번호가 짧습니다.",
"description": "비밀번호를 생성하는 과정에서 비밀번호가 짧으면 안전하지 않습니다" "description": "비밀번호를 생성하는 과정에서 비밀번호가 짧으면 안전하지 않습니다"
@ -597,6 +763,9 @@
"pasteSeed": { "pasteSeed": {
"message": "시드 구문을 이곳에 붙여넣어 주세요!" "message": "시드 구문을 이곳에 붙여넣어 주세요!"
}, },
"pending": {
"message": "펜딩 중"
},
"personalAddressDetected": { "personalAddressDetected": {
"message": "개인 주소가 탐지됨. 토큰 컨트랙트 주소를 입력하세요." "message": "개인 주소가 탐지됨. 토큰 컨트랙트 주소를 입력하세요."
}, },
@ -606,6 +775,9 @@
"popularTokens": { "popularTokens": {
"message": "인기있는 토큰" "message": "인기있는 토큰"
}, },
"prev": {
"message": "이전"
},
"privacyMsg": { "privacyMsg": {
"message": "개인정보 보호 정책" "message": "개인정보 보호 정책"
}, },
@ -622,6 +794,9 @@
"qrCode": { "qrCode": {
"message": "QR 코드 보기" "message": "QR 코드 보기"
}, },
"queue": {
"message": "큐"
},
"readdToken": { "readdToken": {
"message": "옵션 메뉴에서 “토큰 추가”를 눌러서 추후에 다시 이 토큰을 추가하실 수 있습니다." "message": "옵션 메뉴에서 “토큰 추가”를 눌러서 추후에 다시 이 토큰을 추가하실 수 있습니다."
}, },
@ -640,6 +815,18 @@
"refundAddress": { "refundAddress": {
"message": "환불받을 주소" "message": "환불받을 주소"
}, },
"reject": {
"message": "거부"
},
"rejectAll": {
"message": "모두 거부"
},
"rejectTxsN": {
"message": "$1 트랜잭션 거부"
},
"rejectTxsDescription": {
"message": "$1 트랜잭션을 거부합니다."
},
"rejected": { "rejected": {
"message": "거부됨" "message": "거부됨"
}, },
@ -658,14 +845,17 @@
"restoreVault": { "restoreVault": {
"message": "저장소 복구" "message": "저장소 복구"
}, },
"restoreAccountWithSeed": {
"message": "시드 구문으로 계정 복구하기"
},
"required": { "required": {
"message": "필요함" "message": "필요함"
}, },
"retryWithMoreGas": { "retryWithMoreGas": {
"message": "더 높은 가스 가격으로 다시 시도해주세요" "message": "더 높은 가스 가격으로 다시 시도해주세요"
}, },
"walletSeed": { "restore": {
"message": "지갑 시드값" "message": "복구"
}, },
"revealSeedWords": { "revealSeedWords": {
"message": "시드 단어 보이기" "message": "시드 단어 보이기"
@ -685,6 +875,18 @@
"revert": { "revert": {
"message": "되돌림" "message": "되돌림"
}, },
"remove": {
"message": "제거"
},
"removeAccount": {
"message": "계정 제거"
},
"removeAccountDescription": {
"message": "이 계정은 지갑에서 삭제될 것입니다. 지우기 전에 이 계정에 대한 개인 키 혹은 시드 구문을 가지고 있는지 확인하세요. 계정 드롭다운 메뉴를 통해서 계정을 가져오거나 생성할 수 있습니다."
},
"readyToConnect": {
"message": "접속 준비되었나요?"
},
"rinkeby": { "rinkeby": {
"message": "Rinkeby 테스트넷" "message": "Rinkeby 테스트넷"
}, },
@ -694,24 +896,6 @@
"rpc": { "rpc": {
"message": "사용자 정의 RPC" "message": "사용자 정의 RPC"
}, },
"currentRpc": {
"message": "현재 RPC"
},
"connectingToMainnet": {
"message": "이더리움 메인넷 접속중"
},
"connectingToRopsten": {
"message": "Ropsten 테스트넷 접속중"
},
"connectingToKovan": {
"message": "Kovan 테스트넷 접속중"
},
"connectingToRinkeby": {
"message": "Rinkeby 테스트넷 접속중"
},
"connectingToUnknown": {
"message": "알려지지 않은 네트워크 접속중"
},
"sampleAccountName": { "sampleAccountName": {
"message": "예) 나의 새 계정", "message": "예) 나의 새 계정",
"description": "각 계정에 대해서 구별하기 쉬운 이름을 지정하여 사용자가 쉽게 이해할 수 있게 합니다" "description": "각 계정에 대해서 구별하기 쉬운 이름을 지정하여 사용자가 쉽게 이해할 수 있게 합니다"
@ -723,7 +907,7 @@
"message": "트랜잭션 속도 향상하기" "message": "트랜잭션 속도 향상하기"
}, },
"speedUpSubtitle": { "speedUpSubtitle": {
"message": "트랜잭션 가스 가격을 늘려서 해당 트랙잭션에 덮어쓰기 하여 속도를 빠르게 합니다" "message": "트랜잭션 가스 가격을 올려서 해당 트랜잭션에 덮어쓰고 속도를 빠르게 합니다"
}, },
"saveAsCsvFile": { "saveAsCsvFile": {
"message": "CSV 파일로 저장" "message": "CSV 파일로 저장"
@ -735,6 +919,12 @@
"saveSeedAsFile": { "saveSeedAsFile": {
"message": "시드 단어를 파일로 저장하기" "message": "시드 단어를 파일로 저장하기"
}, },
"scanInstructions": {
"message": "QR 코드를 카메라 앞에 가져다 놓아주세요"
},
"scanQrCode": {
"message": "QR 코드 스캔"
},
"search": { "search": {
"message": "검색" "message": "검색"
}, },
@ -756,6 +946,9 @@
"selectCurrency": { "selectCurrency": {
"message": "통화 선택" "message": "통화 선택"
}, },
"selectLocale": {
"message": "언어 선택"
},
"selectService": { "selectService": {
"message": "서비스 선택" "message": "서비스 선택"
}, },
@ -771,25 +964,39 @@
"sendTokens": { "sendTokens": {
"message": "토큰 전송" "message": "토큰 전송"
}, },
"onlySendToEtherAddress": { "sentEther": {
"message": "이더리움 주소로 ETH만 송금하세요." "message": "전송된 이더"
}, },
"onlySendTokensToAccountAddress": { "sentTokens": {
"message": "이더리움계정 주소로 $1 만 보내기.", "message": "전송된 토큰"
"description": "토큰 심볼 보이기" },
"separateEachWord": {
"message": "각 단어는 공백 한칸으로 분리합니다"
}, },
"searchTokens": { "searchTokens": {
"message": "토큰 검색" "message": "토큰 검색"
}, },
"selectAnAddress": {
"message": "주소 선택"
},
"selectAnAccount": {
"message": "계정 선택"
},
"selectAnAccountHelp": {
"message": "메타마스크에서 보기 위한 계정 선택"
},
"selectHdPath": {
"message": "HD 경로 지정"
},
"selectPathHelp": {
"message": "하단에서 Ledger 지갑 계정을 찾지 못하겠으면 \"Legacy (MEW / MyCrypto)\" 경로로 바꿔보세요"
},
"sendTokensAnywhere": { "sendTokensAnywhere": {
"message": "이더 계정로 토큰 전송" "message": "이더 계정로 토큰 전송"
}, },
"settings": { "settings": {
"message": "설정" "message": "설정"
}, },
"info": {
"message": "정보"
},
"shapeshiftBuy": { "shapeshiftBuy": {
"message": "Shapeshift를 통해서 구매하기" "message": "Shapeshift를 통해서 구매하기"
}, },
@ -799,12 +1006,21 @@
"showQRCode": { "showQRCode": {
"message": "QR 코드 보기" "message": "QR 코드 보기"
}, },
"showHexData": {
"message": "Hex 데이터 보기"
},
"showHexDataDescription": {
"message": "선택하면 전송화면의 hex 데이터 필드 값을 보여줍니다."
},
"sign": { "sign": {
"message": "서명" "message": "서명"
}, },
"signed": { "signed": {
"message": "서명됨" "message": "서명됨"
}, },
"signatureRequest": {
"message": "서명 요청"
},
"signMessage": { "signMessage": {
"message": "메시지 서명" "message": "메시지 서명"
}, },
@ -832,6 +1048,24 @@
"stateLogError": { "stateLogError": {
"message": "상태 로그 받기 실패." "message": "상태 로그 받기 실패."
}, },
"step1HardwareWallet": {
"message": "1. 하드웨어 지갑 연결"
},
"step1HardwareWalletMsg": {
"message": "하드웨어 지갑을 컴퓨터에 연결해주세요."
},
"step2HardwareWallet": {
"message": "2. 계정 선택"
},
"step2HardwareWalletMsg": {
"message": "보고 싶은 계정을 선택합니다. 한 번에 하나의 계정만 선택할 수 있습니다."
},
"step3HardwareWallet": {
"message": "3. dApps을 사용하거나 다른 것을 합니다!"
},
"step3HardwareWalletMsg": {
"message": "다른 이더리움 계정을 사용하듯 하드웨어 계정을 사용합니다. dApps을 로그인하거나, 이더를 보내거나, ERC20 토큰 혹은 대체 가능하지 않은 토큰 (예를 들어 CryptoKitties)을 사거나 저장하거나 합니다."
},
"submit": { "submit": {
"message": "제출" "message": "제출"
}, },
@ -879,11 +1113,14 @@
"message": "토큰 기호" "message": "토큰 기호"
}, },
"tokenWarning1": { "tokenWarning1": {
"message": "메타마스크 계좌를 통해 구입한 토큰을 추적합니다. 다른 계정으로 토큰을 구입한 경우 이곳에 나타나지 않습니다." "message": "메타마스크 계정을 통해 구입한 토큰을 추적합니다. 다른 계정으로 토큰을 구입한 경우 이곳에 나타나지 않습니다."
}, },
"total": { "total": {
"message": "합계" "message": "합계"
}, },
"transaction": {
"message": "트랜잭션"
},
"transactions": { "transactions": {
"message": "트랜잭션" "message": "트랜잭션"
}, },
@ -896,13 +1133,22 @@
"transactionNumber": { "transactionNumber": {
"message": "트랜잭션 번호" "message": "트랜잭션 번호"
}, },
"transfer": {
"message": "전송"
},
"transfers": { "transfers": {
"message": "전송" "message": "전송"
}, },
"trezorHardwareWallet": {
"message": "TREZOR 하드웨어 지갑"
},
"troubleTokenBalances": { "troubleTokenBalances": {
"message": "토큰 잔액을 가져오는 데에 문제가 생겼습니다. 링크에서 상세내용을 볼 수 있습니다.", "message": "토큰 잔액을 가져오는 데에 문제가 생겼습니다. 링크에서 상세내용을 볼 수 있습니다.",
"description": "토큰 잔액을 보려면 (here) 링크를 따라가세요" "description": "토큰 잔액을 보려면 (here) 링크를 따라가세요"
}, },
"tryAgain": {
"message": "다시 시도하세요"
},
"twelveWords": { "twelveWords": {
"message": "12개의 단어는 메타마스크 계정을 복구하기 위한 유일한 방법입니다.\n안전한 장소에 보관하시기 바랍니다." "message": "12개의 단어는 메타마스크 계정을 복구하기 위한 유일한 방법입니다.\n안전한 장소에 보관하시기 바랍니다."
}, },
@ -919,16 +1165,34 @@
"message": "허가 안 됨" "message": "허가 안 됨"
}, },
"unavailable": { "unavailable": {
"message": "유효하지 않음" "message": "이용할 수 없음"
},
"units": {
"message": "단위"
}, },
"unknown": { "unknown": {
"message": "알려지지 않음" "message": "알 수 없음"
},
"unknownFunction": {
"message": "알 수 없는 함수"
}, },
"unknownNetwork": { "unknownNetwork": {
"message": "알려지지 않은 프라이빗 네트워크" "message": "알 수 없는 프라이빗 네트워크"
}, },
"unknownNetworkId": { "unknownNetworkId": {
"message": "알려지지 않은 네트워크 ID" "message": "알 수 없는 네트워크 ID"
},
"unknownQrCode": {
"message": "오류: QR 코드를 확인할 수 없습니다"
},
"unknownCameraErrorTitle": {
"message": "이런! 뭔가 잘못되었습니다...."
},
"unknownCameraError": {
"message": "카메라에 접근하는 중 오류가 발생했습니다. 다시 시도해 주세요..."
},
"unlock": {
"message": "잠금 해제"
}, },
"unlockMessage": { "unlockMessage": {
"message": "우리가 기다리던 분권형 웹입니다" "message": "우리가 기다리던 분권형 웹입니다"
@ -956,11 +1220,14 @@
"message": "계정 보기" "message": "계정 보기"
}, },
"viewOnEtherscan": { "viewOnEtherscan": {
"message": "이스캔에서 보기" "message": "이스캔에서 보기"
}, },
"visitWebSite": { "visitWebSite": {
"message": "웹사이트 방문" "message": "웹사이트 방문"
}, },
"walletSeed": {
"message": "지갑 시드값"
},
"warning": { "warning": {
"message": "경고" "message": "경고"
}, },
@ -973,207 +1240,18 @@
"whatsThis": { "whatsThis": {
"message": "이것은 무엇인가요?" "message": "이것은 무엇인가요?"
}, },
"yesLetsTry": {
"message": "네, 시도해보겠습니다."
},
"yourSigRequested": { "yourSigRequested": {
"message": "서명이 요청되고 있습니다." "message": "서명을 요청 중입니다."
}, },
"youSign": { "youSign": {
"message": "서명 중입니다" "message": "서명니다"
}, },
"yourPrivateSeedPhrase": { "yourPrivateSeedPhrase": {
"message": "개인 시드 구문" "message": "개인 시드 구문"
}, },
"accessingYourCamera": {
"message": "카메라 접근중..."
},
"accountSelectionRequired": {
"message": "계정을 선택하셔야 합니다!"
},
"approve": {
"message": "수락"
},
"browserNotSupported": {
"message": "브라우저가 지원하지 않습니다..."
},
"bytes": {
"message": "바이트"
},
"chromeRequiredForHardwareWallets": {
"message": "하드웨어 지갑을 연결하기 위해서는 구글 크롬에서 메타마스크를 사용하셔야 합니다."
},
"connectHardwareWallet": {
"message": "하드웨어 지갑 연결"
},
"connect": {
"message": "연결"
},
"connecting": {
"message": "연결중..."
},
"connectToLedger": {
"message": "Ledger 연결"
},
"connectToTrezor": {
"message": "Trezor 연결"
},
"copyAddress": {
"message": "클립보드로 주소 복사"
},
"downloadGoogleChrome": {
"message": "구글 크롬 다운로드"
},
"dontHaveAHardwareWallet": {
"message": "하드웨어 지갑이 없나요?"
},
"ensNameNotFound": {
"message": "ENS 이름을 찾을 수 없습니다"
},
"parameters": {
"message": "매개변수"
},
"forgetDevice": {
"message": "장치 연결 해제"
},
"functionType": {
"message": "함수 유형"
},
"getHelp": {
"message": "도움말"
},
"hardware": {
"message": "하드웨어"
},
"hardwareWalletConnected": {
"message": "하드웨어 지갑이 연결됨"
},
"hardwareWallets": {
"message": "하드웨어 지갑 연결"
},
"hardwareWalletsMsg": {
"message": "메타마스크에서 사용할 하드웨어 지갑을 선택해주세요"
},
"havingTroubleConnecting": {
"message": "연결에 문제가 있나요?"
},
"hexData": {
"message": "Hex 데이터"
},
"invalidSeedPhrase": {
"message": "잘못된 시드 구문"
},
"ledgerAccountRestriction": {
"message": "새 계정을 추가하려면 최소 마지막 계정을 사용해야 합니다."
},
"menu": {
"message": "메뉴"
},
"noConversionRateAvailable": {
"message": "변환 비율을 찾을 수 없습니다"
},
"notFound": {
"message": "찾을 수 없음"
},
"noWebcamFoundTitle": {
"message": "웹캠이 없습니다"
},
"noWebcamFound": {
"message": "컴퓨터의 웹캠을 찾을 수 없습니다. 다시 시도해보세요."
},
"openInTab": {
"message": "탭으로 열기"
},
"origin": {
"message": "Origin"
},
"prev": {
"message": "이전"
},
"restoreAccountWithSeed": {
"message": "시드 구문으로 계정 복구하기"
},
"restore": {
"message": "복구"
},
"remove": {
"message": "제거"
},
"removeAccount": {
"message": "계정 제거"
},
"removeAccountDescription": {
"message": "이 계정한 지갑에서 삭제될 것입니다. 지우기 전에 이 계정에 대한 개인 키 혹은 시드 구문을 가지고 있는지 확인하세요. 계정 드랍다운 메뉴를 통해서 계정을 가져오거나 생성할 수 있습니다."
},
"readyToConnect": {
"message": "접속 준비되었나요?"
},
"separateEachWord": {
"message": "각 단어는 공백 한칸으로 분리합니다"
},
"orderOneHere": {
"message": "Trezor 혹은 Ledger를 구입하고 자금을 콜드 스토리지에 저장합니다"
},
"selectAnAddress": {
"message": "주소 선택"
},
"selectAnAccount": {
"message": "계정 선택"
},
"selectAnAccountHelp": {
"message": "메타마스크에서 보기위한 계정 선택"
},
"selectHdPath": {
"message": "HD 경로 지정"
},
"selectPathHelp": {
"message": "하단에서 Ledger지갑 계정을 찾지 못하겠으면 \"Legacy (MEW / MyCrypto)\" 경로로 바꿔보세요"
},
"step1HardwareWallet": {
"message": "1. 하드웨어 지갑 연결"
},
"step1HardwareWalletMsg": {
"message": "하드웨어 지갑을 컴퓨터에 연결해주세요."
},
"step2HardwareWallet": {
"message": "2. 계정 선택"
},
"step2HardwareWalletMsg": {
"message": "보고 싶은 계정을 선택합니다. 한번에 하나의 계정만 선택할 수 있습니다."
},
"step3HardwareWallet": {
"message": "3. dApps을 사용하거나 다른 것을 합니다!"
},
"step3HardwareWalletMsg": {
"message": "다른 이더리움 계정을 사용하듯 하드웨어 계정을 사용합니다. dApps을 로그인하거나, 이더를 보내거나, ERC20토큰 혹은 대체가능하지 않은 토큰 (예를 들어 CryptoKitties)을 사거나 저장하거나 합니다."
},
"scanInstructions": {
"message": "QR 코드를 카메라 앞에 가져다 놓아주세요"
},
"scanQrCode": {
"message": "QR 코드 스캔"
},
"transfer": {
"message": "전송"
},
"trezorHardwareWallet": {
"message": "TREZOR 하드웨어 지갑"
},
"tryAgain": {
"message": "다시 시도하세요"
},
"unknownFunction": {
"message": "알 수 없는 함수"
},
"unknownQrCode": {
"message": "오류: QR 코드를 확인할 수 없습니다"
},
"unknownCameraErrorTitle": {
"message": "이런! 뭔가 잘못되었습니다...."
},
"unknownCameraError": {
"message": "카메라를 접근하는 중 오류가 발생했습니다. 다시 시도해 주세요..."
},
"unlock": {
"message": "잠금 해제"
},
"youNeedToAllowCameraAccess": { "youNeedToAllowCameraAccess": {
"message": "이 기능을 사용하려면 카메라 접근을 허용해야 합니다." "message": "이 기능을 사용하려면 카메라 접근을 허용해야 합니다."
} }

File diff suppressed because it is too large Load Diff

14
app/images/eth.svg Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
<style type="text/css">
.st0{fill:#38393A;}
</style>
<title>deposit-eth</title>
<desc>Created with Sketch.</desc>
<g id="deposit-eth" transform="translate(0.000000, 14.000000)">
<path id="Shape" class="st0" d="M19.9,16L7.5,8.7L19.9,26L32.3,8.7L19.9,16L19.9,16z M20.1-14L7.7,6.4l12.4,7.3l12.4-7.2L20.1-14z"
/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 670 B

13
app/images/expand.svg Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>expand</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Extension-(mobile-form-factor)" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="expand" fill="#FFFFFF" fill-rule="nonzero">
<path d="M15.4802977,0.0647543729 C15.3777028,0.0227060788 15.2692212,0 15.1590578,0 L10.1134046,0 C9.64920448,0 9.27246238,0.376752715 9.27246238,0.840965882 C9.27246238,1.30602001 9.64920448,1.68193176 10.1134046,1.68193176 L13.1290233,1.68193176 L8.77993963,6.03113791 C8.45113123,6.35995557 8.45113123,6.89228697 8.77993963,7.22026366 C8.94392336,7.38509298 9.15920457,7.46666667 9.37448577,7.46666667 C9.58976698,7.46666667 9.80504818,7.38509298 9.96903191,7.22026366 L14.3181156,2.87105752 L14.3181156,5.88676117 C14.3181156,6.35181531 14.6948577,6.72772705 15.1590578,6.72772705 C15.6232579,6.72772705 16,6.35181531 16,5.88676117 L16,0.840965882 C16,0.731640317 15.9781355,0.623155718 15.9352475,0.519716915 C15.8494713,0.31452124 15.6863286,0.150532893 15.4802977,0.0647543729 Z" id="Fill-1" transform="translate(12.266667, 3.733333) rotate(-360.000000) translate(-12.266667, -3.733333) "></path>
<path d="M6.94696439,8.59808771 C6.84436944,8.55603941 6.73588789,8.53333333 6.62572446,8.53333333 L1.58007124,8.53333333 C1.11587115,8.53333333 0.739129042,8.91008605 0.739129042,9.37429922 C0.739129042,9.83935335 1.11587115,10.2152651 1.58007124,10.2152651 L4.59568999,10.2152651 L0.246606301,14.5644712 C-0.0822021004,14.8932889 -0.0822021004,15.4256203 0.246606301,15.753597 C0.410590031,15.9184263 0.625871235,16 0.841152439,16 C1.05643364,16 1.27171485,15.9184263 1.43569858,15.753597 L5.78478226,11.4043909 L5.78478226,14.4200945 C5.78478226,14.8851486 6.16152437,15.2610604 6.62572446,15.2610604 C7.08992456,15.2610604 7.46666667,14.8851486 7.46666667,14.4200945 L7.46666667,9.37429922 C7.46666667,9.26497365 7.44480217,9.15648905 7.40191412,9.05305025 C7.31613801,8.84785457 7.15299522,8.68386623 6.94696439,8.59808771 Z" id="Fill-1-Copy" transform="translate(3.733333, 12.266667) rotate(-180.000000) translate(-3.733333, -12.266667) "></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

16
app/images/hide.svg Normal file
View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="12px" viewBox="0 0 16 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>hide</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Extension-(mobile-form-factor)" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="hide" fill="#FFFFFF">
<polygon id="Fill-1" points="12.7924541 0.685525644 2.5021101 11.2520394 3.21020258 11.978038 13.4995465 1.41152421"></polygon>
<path d="M4.53087507,6.33167913 C4.53087507,4.36419221 6.08507807,2.76945986 8.00132835,2.76945986 C8.40338086,2.76945986 8.78243037,2.85366337 9.14147727,2.98202238 L10.0225923,2.07734807 C9.36950705,1.93153223 8.69641914,1.83808687 8.00132835,1.83808687 C4.64088944,1.83808687 1.68850383,3.6289518 0.000283333333,6.33167913 C0.757382218,7.54338819 1.77751546,8.55691095 2.96667077,9.32295752 L4.73890224,7.50334018 C4.61288579,7.1346931 4.53087507,6.74448171 4.53087507,6.33167913" id="Fill-2"></path>
<path d="M13.0194751,3.34029805 L11.2513649,5.16094226 C11.3770883,5.52958934 11.4589083,5.91980074 11.4589083,6.33157644 C11.4589083,8.30009024 9.90831963,9.89482259 7.99652557,9.89482259 C7.59441023,9.89482259 7.21724,9.81061908 6.85803026,9.68123319 L5.97896421,10.5859075 C6.63152857,10.7317233 7.30305122,10.8261956 7.99652557,10.8261956 C11.348152,10.8261956 14.2936719,9.03430378 15.9789642,6.33157644 C15.223626,5.11986738 14.2048672,4.10634463 13.0194751,3.34029805" id="Fill-4"></path>
<path d="M8.00102831,8.7499629 C9.30219826,8.7499629 10.3553358,7.66763972 10.3553358,6.33167913 C10.3553358,6.25774434 10.341334,6.18689017 10.3343331,6.11398225 L7.78900062,8.72839858 C7.86000989,8.73455982 7.92901891,8.7499629 8.00102831,8.7499629" id="Fill-7"></path>
<path d="M8.00102831,3.9139088 C6.69985837,3.9139088 5.64672082,4.99623198 5.64672082,6.33116569 C5.64672082,6.40612736 5.66072264,6.47698153 5.66772356,6.54886258 L8.213056,3.93547311 C8.14204673,3.92931188 8.07303772,3.9139088 8.00102831,3.9139088" id="Fill-9"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

17
app/images/info.svg Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>info</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Extension-(mobile-form-factor)" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="info" fill="#FFFFFF">
<path d="M13.2142857,0.642857143 L13.2142857,0.5 L0.5,0.5 L0.5,13.2142857 L0.642857143,13.2142857 L0.642857143,0.642857143 L13.2142857,0.642857143 Z" id="Combined-Shape" stroke="#FFFFFF"></path>
<path d="M3.42857143,3.42857143 L3.42857143,14.8571429 L14.8571429,14.8571429 L14.8571429,3.42857143 L3.42857143,3.42857143 Z M2.28571429,2.28571429 L16,2.28571429 L16,16 L2.28571429,16 L2.28571429,2.28571429 Z" id="Rectangle-18-Copy" fill-rule="nonzero"></path>
<g id="Group-44" transform="translate(8.000000, 5.000000)">
<rect id="Rectangle-39" x="0" y="3" width="2" height="5" rx="1"></rect>
<rect id="Rectangle-39-Copy" x="0" y="0" width="2" height="2" rx="1"></rect>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>open-etherscan</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Extension-(mobile-form-factor)" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="open-etherscan" fill="#FFFFFF">
<path d="M3.00020312,0 C2.44821175,0 2.00021875,0.447993 2.00021875,0.999984375 C2.00021875,1.55197575 2.44821175,1.99996875 3.00020312,1.99996875 L12.5860533,1.99996875 L0.293245418,14.2927767 C-0.0977484727,14.6837706 -0.0977484727,15.3157607 0.293245418,15.7067546 C0.488242371,15.9017515 0.744238371,15.99975 1.00023437,15.99975 C1.25623037,15.99975 1.51222637,15.9017515 1.70722332,15.7067546 L14.0000312,3.41394666 L14.0000312,12.9997969 C14.0000312,13.5517883 14.4480242,13.9997813 15.0000156,13.9997813 C15.552007,13.9997813 16,13.5517883 16,12.9997969 L16,0 L3.00020312,0 Z"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,7 +1,7 @@
{ {
"name": "__MSG_appName__", "name": "__MSG_appName__",
"short_name": "__MSG_appName__", "short_name": "__MSG_appName__",
"version": "4.12.0", "version": "4.16.0",
"manifest_version": 2, "manifest_version": 2,
"author": "https://metamask.io", "author": "https://metamask.io",
"description": "__MSG_appDescription__", "description": "__MSG_appDescription__",

View File

@ -2,6 +2,9 @@
* @file The entry point for the web extension singleton process. * @file The entry point for the web extension singleton process.
*/ */
// this needs to run before anything else
require('./lib/setupFetchDebugging')()
const urlUtil = require('url') const urlUtil = require('url')
const endOfStream = require('end-of-stream') const endOfStream = require('end-of-stream')
const pump = require('pump') const pump = require('pump')

View File

@ -135,17 +135,22 @@ function doctypeCheck () {
} }
/** /**
* Checks the current document extension * Returns whether or not the extension (suffix) of the current document is prohibited
* *
* @returns {boolean} {@code true} if the current extension is not prohibited * This checks {@code window.location.pathname} against a set of file extensions
* that should not have web3 injected into them. This check is indifferent of query parameters
* in the location.
*
* @returns {boolean} whether or not the extension of the current document is prohibited
*/ */
function suffixCheck () { function suffixCheck () {
var prohibitedTypes = ['xml', 'pdf'] const prohibitedTypes = [
var currentUrl = window.location.href /\.xml$/,
var currentRegex /\.pdf$/,
]
const currentUrl = window.location.pathname
for (let i = 0; i < prohibitedTypes.length; i++) { for (let i = 0; i < prohibitedTypes.length; i++) {
currentRegex = new RegExp(`\\.${prohibitedTypes[i]}$`) if (prohibitedTypes[i].test(currentUrl)) {
if (currentRegex.test(currentUrl)) {
return false return false
} }
} }

View File

@ -38,6 +38,9 @@ class PreferencesController {
lostIdentities: {}, lostIdentities: {},
seedWords: null, seedWords: null,
forgottenPassword: false, forgottenPassword: false,
preferences: {
useETHAsPrimaryCurrency: true,
},
}, opts.initState) }, opts.initState)
this.diagnostics = opts.diagnostics this.diagnostics = opts.diagnostics
@ -371,22 +374,6 @@ class PreferencesController {
return Promise.resolve(label) return Promise.resolve(label)
} }
/**
* Gets an updated rpc list from this.addToFrequentRpcList() and sets the `frequentRpcList` to this update list.
*
* @param {string} _url The the new rpc url to add to the updated list
* @param {bool} remove Remove selected url
* @returns {Promise<void>} Promise resolves with undefined
*
*/
updateFrequentRpcList (_url, remove = false) {
return this.addToFrequentRpcList(_url, remove)
.then((rpcList) => {
this.store.updateState({ frequentRpcList: rpcList })
return Promise.resolve()
})
}
/** /**
* Setter for the `currentAccountTab` property * Setter for the `currentAccountTab` property
* *
@ -402,24 +389,39 @@ class PreferencesController {
} }
/** /**
* Returns an updated rpcList based on the passed url and the current list. * Adds custom RPC url to state.
* The returned list will have a max length of 3. If the _url currently exists it the list, it will be moved to the
* end of the list. The current list is modified and returned as a promise.
* *
* @param {string} _url The rpc url to add to the frequentRpcList. * @param {string} url The RPC url to add to frequentRpcList.
* @param {bool} remove Remove selected url * @returns {Promise<array>} Promise resolving to updated frequentRpcList.
* @returns {Promise<array>} The updated frequentRpcList.
* *
*/ */
addToFrequentRpcList (_url, remove = false) { addToFrequentRpcList (url) {
const rpcList = this.getFrequentRpcList() const rpcList = this.getFrequentRpcList()
const index = rpcList.findIndex((element) => { return element === _url }) const index = rpcList.findIndex((element) => { return element === url })
if (index !== -1) { if (index !== -1) {
rpcList.splice(index, 1) rpcList.splice(index, 1)
} }
if (!remove && _url !== 'http://localhost:8545') { if (url !== 'http://localhost:8545') {
rpcList.push(_url) rpcList.push(url)
} }
this.store.updateState({ frequentRpcList: rpcList })
return Promise.resolve(rpcList)
}
/**
* Removes custom RPC url from state.
*
* @param {string} url The RPC url to remove from frequentRpcList.
* @returns {Promise<array>} Promise resolving to updated frequentRpcList.
*
*/
removeFromFrequentRpcList (url) {
const rpcList = this.getFrequentRpcList()
const index = rpcList.findIndex((element) => { return element === url })
if (index !== -1) {
rpcList.splice(index, 1)
}
this.store.updateState({ frequentRpcList: rpcList })
return Promise.resolve(rpcList) return Promise.resolve(rpcList)
} }
@ -463,6 +465,33 @@ class PreferencesController {
getFeatureFlags () { getFeatureFlags () {
return this.store.getState().featureFlags return this.store.getState().featureFlags
} }
/**
* Updates the `preferences` property, which is an object. These are user-controlled features
* found in the settings page.
* @param {string} preference The preference to enable or disable.
* @param {boolean} value Indicates whether or not the preference should be enabled or disabled.
* @returns {Promise<object>} Promises a new object; the updated preferences object.
*/
setPreference (preference, value) {
const currentPreferences = this.getPreferences()
const updatedPreferences = {
...currentPreferences,
[preference]: value,
}
this.store.updateState({ preferences: updatedPreferences })
return Promise.resolve(updatedPreferences)
}
/**
* A getter for the `preferences` property
* @returns {object} A key-boolean map of user-selected preferences.
*/
getPreferences () {
return this.store.getState().preferences
}
// //
// PRIVATE METHODS // PRIVATE METHODS
// //

View File

@ -166,6 +166,10 @@ class TransactionController extends EventEmitter {
async addUnapprovedTransaction (txParams) { async addUnapprovedTransaction (txParams) {
// validate // validate
const normalizedTxParams = txUtils.normalizeTxParams(txParams) const normalizedTxParams = txUtils.normalizeTxParams(txParams)
// Assert the from address is the selected address
if (normalizedTxParams.from !== this.getSelectedAddress()) {
throw new Error(`Transaction from address isn't valid for this account`)
}
txUtils.validateTxParams(normalizedTxParams) txUtils.validateTxParams(normalizedTxParams)
// construct txMeta // construct txMeta
let txMeta = this.txStateManager.generateTxMeta({ let txMeta = this.txStateManager.generateTxMeta({
@ -362,7 +366,40 @@ class TransactionController extends EventEmitter {
this.txStateManager.setTxStatusSubmitted(txId) this.txStateManager.setTxStatusSubmitted(txId)
} }
confirmTransaction (txId) { /**
* Sets the status of the transaction to confirmed and sets the status of nonce duplicates as
* dropped if the txParams have data it will fetch the txReceipt
* @param {number} txId - The tx's ID
* @returns {Promise<void>}
*/
async confirmTransaction (txId) {
// get the txReceipt before marking the transaction confirmed
// to ensure the receipt is gotten before the ui revives the tx
const txMeta = this.txStateManager.getTx(txId)
if (!txMeta) {
return
}
try {
const txReceipt = await this.query.getTransactionReceipt(txMeta.hash)
// It seems that sometimes the numerical values being returned from
// this.query.getTransactionReceipt are BN instances and not strings.
const gasUsed = typeof txReceipt.gasUsed !== 'string'
? txReceipt.gasUsed.toString(16)
: txReceipt.gasUsed
txMeta.txReceipt = {
...txReceipt,
gasUsed,
}
this.txStateManager.updateTx(txMeta, 'transactions#confirmTransaction - add txReceipt')
} catch (err) {
log.error(err)
}
this.txStateManager.setTxStatusConfirmed(txId) this.txStateManager.setTxStatusConfirmed(txId)
this._markNonceDuplicatesDropped(txId) this._markNonceDuplicatesDropped(txId)
} }

View File

@ -400,6 +400,11 @@ class TransactionStateManager extends EventEmitter {
*/ */
_setTxStatus (txId, status) { _setTxStatus (txId, status) {
const txMeta = this.getTx(txId) const txMeta = this.getTx(txId)
if (!txMeta) {
return
}
txMeta.status = status txMeta.status = status
setTimeout(() => { setTimeout(() => {
try { try {

View File

@ -27,6 +27,8 @@ var metamaskStream = new LocalMessageDuplexStream({
// compose the inpage provider // compose the inpage provider
var inpageProvider = new MetamaskInpageProvider(metamaskStream) 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 // Augment the provider with its enable method
inpageProvider.enable = function (options = {}) { inpageProvider.enable = function (options = {}) {

View File

@ -0,0 +1,34 @@
module.exports = setupFetchDebugging
//
// This is a utility to help resolve cases where `window.fetch` throws a
// `TypeError: Failed to Fetch` without any stack or context for the request
// https://github.com/getsentry/sentry-javascript/pull/1293
//
function setupFetchDebugging() {
if (!global.fetch) return
const originalFetch = global.fetch
global.fetch = wrappedFetch
async function wrappedFetch(...args) {
const initialStack = getCurrentStack()
try {
return await originalFetch.call(window, ...args)
} catch (err) {
console.warn('FetchDebugger - fetch encountered an Error', err)
console.warn('FetchDebugger - overriding stack to point of original call')
err.stack = initialStack
throw err
}
}
}
function getCurrentStack() {
try {
throw new Error('Fake error for generating stack trace')
} catch (err) {
return err.stack
}
}

View File

@ -129,6 +129,7 @@ module.exports = class MetamaskController extends EventEmitter {
provider: this.provider, provider: this.provider,
blockTracker: this.blockTracker, blockTracker: this.blockTracker,
}) })
// start and stop polling for balances based on activeControllerConnections // start and stop polling for balances based on activeControllerConnections
this.on('controllerConnectionChanged', (activeControllerConnections) => { this.on('controllerConnectionChanged', (activeControllerConnections) => {
if (activeControllerConnections > 0) { if (activeControllerConnections > 0) {
@ -138,6 +139,11 @@ module.exports = class MetamaskController extends EventEmitter {
} }
}) })
// ensure accountTracker updates balances after network change
this.networkController.on('networkDidChange', () => {
this.accountTracker._updateAccounts()
})
// key mgmt // key mgmt
const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring] const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring]
this.keyringController = new KeyringController({ this.keyringController = new KeyringController({
@ -387,6 +393,7 @@ module.exports = class MetamaskController extends EventEmitter {
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController), setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController), setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController),
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController), setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
setPreference: nodeify(preferencesController.setPreference, preferencesController),
// BlacklistController // BlacklistController
whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this), whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this),
@ -1451,7 +1458,7 @@ module.exports = class MetamaskController extends EventEmitter {
*/ */
async setCustomRpc (rpcTarget) { async setCustomRpc (rpcTarget) {
this.networkController.setRpcTarget(rpcTarget) this.networkController.setRpcTarget(rpcTarget)
await this.preferencesController.updateFrequentRpcList(rpcTarget) await this.preferencesController.addToFrequentRpcList(rpcTarget)
return rpcTarget return rpcTarget
} }
@ -1460,7 +1467,7 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {string} rpcTarget - A RPC URL to delete. * @param {string} rpcTarget - A RPC URL to delete.
*/ */
async delCustomRpc (rpcTarget) { async delCustomRpc (rpcTarget) {
await this.preferencesController.updateFrequentRpcList(rpcTarget, true) await this.preferencesController.removeFromFrequentRpcList(rpcTarget)
} }
/** /**

View File

@ -14,21 +14,27 @@ async function start () {
const versionAlreadyExists = await checkIfVersionExists() const versionAlreadyExists = await checkIfVersionExists()
// abort if versions exists // abort if versions exists
if (versionAlreadyExists) { if (versionAlreadyExists) {
console.log(`Version "${VERSION}" already exists on Sentry, aborting sourcemap upload.`) console.log(`Version "${VERSION}" already exists on Sentry, skipping version creation`)
return } else {
}
// create sentry release // create sentry release
console.log(`creating Sentry release for "${VERSION}"...`) console.log(`creating Sentry release for "${VERSION}"...`)
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' new ${VERSION}`) await exec(`sentry-cli releases --org 'metamask' --project 'metamask' new ${VERSION}`)
console.log(`removing any existing files from Sentry release "${VERSION}"...`) console.log(`removing any existing files from Sentry release "${VERSION}"...`)
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} delete --all`) await exec(`sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} delete --all`)
}
// check if version has artifacts or not
const versionHasArtifacts = versionAlreadyExists && await checkIfVersionHasArtifacts()
if (!versionHasArtifacts) {
// upload sentry source and sourcemaps // upload sentry source and sourcemaps
console.log(`uploading source files Sentry release "${VERSION}"...`) console.log(`uploading source files Sentry release "${VERSION}"...`)
await exec(`for FILEPATH in ./dist/chrome/*.js; do [ -e $FILEPATH ] || continue; export FILE=\`basename $FILEPATH\` && echo uploading $FILE && sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} upload $FILEPATH metamask/$FILE; done;`) await exec(`for FILEPATH in ./dist/chrome/*.js; do [ -e $FILEPATH ] || continue; export FILE=\`basename $FILEPATH\` && echo uploading $FILE && sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} upload $FILEPATH metamask/$FILE; done;`)
console.log(`uploading sourcemaps Sentry release "${VERSION}"...`) console.log(`uploading sourcemaps Sentry release "${VERSION}"...`)
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} upload-sourcemaps ./dist/sourcemaps/ --url-prefix 'sourcemaps'`) await exec(`sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} upload-sourcemaps ./dist/sourcemaps/ --url-prefix 'sourcemaps'`)
console.log('all done!') console.log('all done!')
} else {
console.log(`Version "${VERSION}" already has artifacts on Sentry, skipping sourcemap upload`)
}
} }
async function checkIfAuthWorks () { async function checkIfAuthWorks () {
@ -45,6 +51,12 @@ async function checkIfVersionExists () {
return versionAlreadyExists return versionAlreadyExists
} }
async function checkIfVersionHasArtifacts () {
const artifacts = await exec(`sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} list`)
// When there's no artifacts, we get a response from the shell like this ['', '']
return artifacts[0] && artifacts[0].length > 0
}
async function doesNotFail (asyncFn) { async function doesNotFail (asyncFn) {
try { try {
await asyncFn() await asyncFn()

View File

@ -107,7 +107,10 @@
"maxModeOn": false, "maxModeOn": false,
"editingTransactionId": null "editingTransactionId": null
}, },
"currentLocale": "en" "currentLocale": "en",
"preferences": {
"useETHAsPrimaryCurrency": true
}
}, },
"appState": { "appState": {
"menuOpen": false, "menuOpen": false,

View File

@ -150,7 +150,10 @@
"maxModeOn": false, "maxModeOn": false,
"editingTransactionId": null "editingTransactionId": null
}, },
"currentLocale": "en" "currentLocale": "en",
"preferences": {
"useETHAsPrimaryCurrency": true
}
}, },
"appState": { "appState": {
"menuOpen": false, "menuOpen": false,

View File

@ -108,7 +108,10 @@
"maxModeOn": false, "maxModeOn": false,
"editingTransactionId": null "editingTransactionId": null
}, },
"currentLocale": "en" "currentLocale": "en",
"preferences": {
"useETHAsPrimaryCurrency": true
}
}, },
"appState": { "appState": {
"menuOpen": false, "menuOpen": false,

View File

@ -37,7 +37,10 @@
"shapeShiftTxList": [], "shapeShiftTxList": [],
"lostAccounts": [], "lostAccounts": [],
"tokens": [], "tokens": [],
"currentLocale": "en" "currentLocale": "en",
"preferences": {
"useETHAsPrimaryCurrency": true
}
}, },
"appState": { "appState": {
"menuOpen": false, "menuOpen": false,

View File

@ -109,7 +109,10 @@
"maxModeOn": false, "maxModeOn": false,
"editingTransactionId": null "editingTransactionId": null
}, },
"currentLocale": "en" "currentLocale": "en",
"preferences": {
"useETHAsPrimaryCurrency": true
}
}, },
"appState": { "appState": {
"menuOpen": false, "menuOpen": false,

View File

@ -102,7 +102,10 @@
"shapeShiftTxList": [{"depositAddress":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke","depositType":"BTC","key":"shapeshift","response":{"status":"no_deposits","address":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke"},"time":1522377459106}], "shapeShiftTxList": [{"depositAddress":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke","depositType":"BTC","key":"shapeshift","response":{"status":"no_deposits","address":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke"},"time":1522377459106}],
"lostAccounts": [], "lostAccounts": [],
"send": {}, "send": {},
"currentLocale": "en" "currentLocale": "en",
"preferences": {
"useETHAsPrimaryCurrency": true
}
}, },
"appState": { "appState": {
"menuOpen": false, "menuOpen": false,

View File

@ -462,7 +462,9 @@ function generateBundler (opts, performBundle) {
bundler.transform(envify({ bundler.transform(envify({
METAMASK_DEBUG: opts.devMode, METAMASK_DEBUG: opts.devMode,
NODE_ENV: opts.devMode ? 'development' : 'production', NODE_ENV: opts.devMode ? 'development' : 'production',
})) }), {
global: true,
})
if (opts.watch) { if (opts.watch) {
bundler = watchify(bundler) bundler = watchify(bundler)

View File

@ -17,7 +17,7 @@
font-family: Roboto; font-family: Roboto;
} }
@media screen and (min-height: 576px) { @media screen and (min-height: 601px) {
.first-time-flow { .first-time-flow {
height: 100vh; height: 100vh;
} }

854
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,7 @@
"test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara", "test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara",
"test:integration:build": "gulp build:scss", "test:integration:build": "gulp build:scss",
"test:e2e:chrome": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:e2e:run:chrome'", "test:e2e:chrome": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:e2e:run:chrome'",
"test:e2e:drizzle:beta": "SELENIUM_BROWSER=chrome test/e2e/beta/run-drizzle.sh",
"test:e2e:chrome:beta": "SELENIUM_BROWSER=chrome test/e2e/beta/run-all.sh", "test:e2e:chrome:beta": "SELENIUM_BROWSER=chrome test/e2e/beta/run-all.sh",
"test:e2e:firefox": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:e2e:run:firefox'", "test:e2e:firefox": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:e2e:run:firefox'",
"test:e2e:firefox:beta": "SELENIUM_BROWSER=firefox test/e2e/beta/run-all.sh", "test:e2e:firefox:beta": "SELENIUM_BROWSER=firefox test/e2e/beta/run-all.sh",
@ -123,7 +124,7 @@
"eth-phishing-detect": "^1.1.4", "eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2", "eth-query": "^2.1.2",
"eth-sig-util": "^2.0.2", "eth-sig-util": "^2.0.2",
"eth-token-tracker": "^1.1.4", "eth-token-tracker": "^1.1.5",
"eth-trezor-keyring": "^0.1.0", "eth-trezor-keyring": "^0.1.0",
"ethereumjs-abi": "^0.6.4", "ethereumjs-abi": "^0.6.4",
"ethereumjs-tx": "^1.3.0", "ethereumjs-tx": "^1.3.0",
@ -142,7 +143,7 @@
"fast-levenshtein": "^2.0.6", "fast-levenshtein": "^2.0.6",
"file-loader": "^1.1.11", "file-loader": "^1.1.11",
"fuse.js": "^3.2.0", "fuse.js": "^3.2.0",
"gulp": "github:gulpjs/gulp#4.0", "gulp": "github:gulpjs/gulp#v4.0.0",
"gulp-autoprefixer": "^5.0.0", "gulp-autoprefixer": "^5.0.0",
"gulp-debug": "^3.2.0", "gulp-debug": "^3.2.0",
"gulp-eslint": "^4.0.0", "gulp-eslint": "^4.0.0",
@ -260,8 +261,9 @@
"eslint-plugin-json": "^1.2.0", "eslint-plugin-json": "^1.2.0",
"eslint-plugin-mocha": "^5.0.0", "eslint-plugin-mocha": "^5.0.0",
"eslint-plugin-react": "^7.4.0", "eslint-plugin-react": "^7.4.0",
"eth-json-rpc-middleware": "^3.1.1", "eth-json-rpc-middleware": "^3.1.3",
"eth-keyring-controller": "^3.3.1", "eth-keyring-controller": "^3.3.1",
"fetch-mock": "^6.5.2",
"file-loader": "^1.1.11", "file-loader": "^1.1.11",
"fs-extra": "^6.0.1", "fs-extra": "^6.0.1",
"fs-promise": "^2.0.3", "fs-promise": "^2.0.3",

70
test/data/2-state.json Normal file
View File

@ -0,0 +1,70 @@
{ "isInitialized": true,
"provider": { "type": "rpc", "rpcTarget": "http://localhost:8545" },
"network": "loading",
"accounts": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"balance": "0x0"
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b",
"balance": "0x0"
}
},
"currentBlockGasLimit": "",
"unapprovedTxs": {},
"selectedAddressTxList": [],
"computedBalances": {},
"unapprovedMsgs": {},
"unapprovedMsgCount": 0,
"unapprovedPersonalMsgs": {},
"unapprovedPersonalMsgCount": 0,
"unapprovedTypedMessages": {},
"unapprovedTypedMessagesCount": 0,
"isUnlocked": true,
"keyringTypes": [ "Simple Key Pair", "HD Key Tree" ],
"keyrings":[
{ "type": "HD Key Tree",
"accounts": [
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
]
},
{
"type": "Simple Key Pair",
"accounts": [
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
]
}
],
"frequentRpcList": [],
"currentAccountTab": "history",
"tokens": [],
"useBlockie": false,
"featureFlags": {},
"currentLocale": null,
"identities": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"name": "Account 1",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"name": "Account 2",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
}
},
"lostIdentities": {},
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"recentBlocks": [],
"addressBook": [],
"currentCurrency": "usd",
"conversionRate": 288.45,
"conversionDate": 1506444677,
"nextUnreadNotice": null,
"noActiveNotices": true,
"shapeShiftTxList": [],
"infuraNetworkStatus": {},
"lostAccounts": [],
"seedWords": "debris dizzy just program just float decrease vacant alarm reduce speak stadium",
"forgottenPassword": null
}

View File

@ -0,0 +1,286 @@
const path = require('path')
const assert = require('assert')
const webdriver = require('selenium-webdriver')
const { By, until } = webdriver
const {
delay,
buildChromeWebDriver,
buildFirefoxWebdriver,
installWebExt,
getExtensionIdChrome,
getExtensionIdFirefox,
} = require('../func')
const {
checkBrowserForConsoleErrors,
closeAllWindowHandlesExcept,
findElement,
findElements,
loadExtension,
openNewPage,
verboseReportOnFailure,
waitUntilXWindowHandles,
} = require('./helpers')
describe('MetaMask', function () {
let extensionId
let driver
const tinyDelayMs = 200
const regularDelayMs = tinyDelayMs * 2
const largeDelayMs = regularDelayMs * 2
this.timeout(0)
this.bail(true)
before(async function () {
switch (process.env.SELENIUM_BROWSER) {
case 'chrome': {
const extPath = path.resolve('dist/chrome')
driver = buildChromeWebDriver(extPath)
extensionId = await getExtensionIdChrome(driver)
await driver.get(`chrome-extension://${extensionId}/popup.html`)
break
}
case 'firefox': {
const extPath = path.resolve('dist/firefox')
driver = buildFirefoxWebdriver()
await installWebExt(driver, extPath)
await delay(700)
extensionId = await getExtensionIdFirefox(driver)
await driver.get(`moz-extension://${extensionId}/popup.html`)
}
}
})
afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await checkBrowserForConsoleErrors(driver)
if (errors.length) {
const errorReports = errors.map(err => err.message)
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
console.error(new Error(errorMessage))
}
}
if (this.currentTest.state === 'failed') {
await verboseReportOnFailure(driver, this.currentTest)
}
})
after(async function () {
await driver.quit()
})
describe('New UI setup', async function () {
it('switches to first tab', async function () {
await delay(tinyDelayMs)
const [firstTab] = await driver.getAllWindowHandles()
await driver.switchTo().window(firstTab)
await delay(regularDelayMs)
})
it('selects the new UI option', async () => {
try {
const overlay = await findElement(driver, By.css('.full-flex-height'))
await driver.wait(until.stalenessOf(overlay))
} catch (e) {}
let button
try {
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
} catch (e) {
await loadExtension(driver, extensionId)
await delay(largeDelayMs)
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
}
await button.click()
await delay(regularDelayMs)
// Close all other tabs
const [tab0, tab1, tab2] = await driver.getAllWindowHandles()
await driver.switchTo().window(tab0)
await delay(tinyDelayMs)
let selectedUrl = await driver.getCurrentUrl()
await delay(tinyDelayMs)
if (tab0 && selectedUrl.match(/popup.html/)) {
await closeAllWindowHandlesExcept(driver, tab0)
} else if (tab1) {
await driver.switchTo().window(tab1)
selectedUrl = await driver.getCurrentUrl()
await delay(tinyDelayMs)
if (selectedUrl.match(/popup.html/)) {
await closeAllWindowHandlesExcept(driver, tab1)
} else if (tab2) {
await driver.switchTo().window(tab2)
selectedUrl = await driver.getCurrentUrl()
selectedUrl.match(/popup.html/) && await closeAllWindowHandlesExcept(driver, tab2)
}
} else {
throw new Error('popup.html not found')
}
await delay(regularDelayMs)
const [appTab] = await driver.getAllWindowHandles()
await driver.switchTo().window(appTab)
await delay(tinyDelayMs)
await loadExtension(driver, extensionId)
await delay(regularDelayMs)
const continueBtn = await findElement(driver, By.css('.welcome-screen__button'))
await continueBtn.click()
await delay(regularDelayMs)
})
})
describe('Going through the first time flow', () => {
it('accepts a secure password', async () => {
const passwordBox = await findElement(driver, By.css('.create-password #create-password'))
const passwordBoxConfirm = await findElement(driver, By.css('.create-password #confirm-password'))
const button = await findElement(driver, By.css('.create-password button'))
await passwordBox.sendKeys('correct horse battery staple')
await passwordBoxConfirm.sendKeys('correct horse battery staple')
await button.click()
await delay(regularDelayMs)
})
it('clicks through the unique image screen', async () => {
const nextScreen = await findElement(driver, By.css('.unique-image button'))
await nextScreen.click()
await delay(regularDelayMs)
})
it('clicks through the ToS', async () => {
// terms of use
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
assert.equal(canClickThrough, false, 'disabled continue button')
const bottomOfTos = await findElement(driver, By.linkText('Attributions'))
await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos)
await delay(regularDelayMs)
const acceptTos = await findElement(driver, By.css('.tou button'))
driver.wait(until.elementIsEnabled(acceptTos))
await acceptTos.click()
await delay(regularDelayMs)
})
it('clicks through the privacy notice', async () => {
// privacy notice
const nextScreen = await findElement(driver, By.css('.tou button'))
await nextScreen.click()
await delay(regularDelayMs)
})
it('clicks through the phishing notice', async () => {
// phishing notice
const noticeElement = await driver.findElement(By.css('.markdown'))
await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', noticeElement)
await delay(regularDelayMs)
const nextScreen = await findElement(driver, By.css('.tou button'))
await nextScreen.click()
await delay(regularDelayMs)
})
let seedPhrase
it('reveals the seed phrase', async () => {
const byRevealButton = By.css('.backup-phrase__secret-blocker .backup-phrase__reveal-button')
await driver.wait(until.elementLocated(byRevealButton, 10000))
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000)
await revealSeedPhraseButton.click()
await delay(regularDelayMs)
seedPhrase = await driver.findElement(By.css('.backup-phrase__secret-words')).getText()
assert.equal(seedPhrase.split(' ').length, 12)
await delay(regularDelayMs)
const nextScreen = await findElement(driver, By.css('.backup-phrase button'))
await nextScreen.click()
await delay(regularDelayMs)
})
async function clickWordAndWait (word) {
const xpathClass = 'backup-phrase__confirm-seed-option backup-phrase__confirm-seed-option--unselected'
const xpath = `//button[@class='${xpathClass}' and contains(text(), '${word}')]`
const word0 = await findElement(driver, By.xpath(xpath), 10000)
await word0.click()
await delay(tinyDelayMs)
}
async function retypeSeedPhrase (words, wasReloaded, count = 0) {
try {
if (wasReloaded) {
const byRevealButton = By.css('.backup-phrase__secret-blocker .backup-phrase__reveal-button')
await driver.wait(until.elementLocated(byRevealButton, 10000))
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000)
await revealSeedPhraseButton.click()
await delay(regularDelayMs)
const nextScreen = await findElement(driver, By.css('.backup-phrase button'))
await nextScreen.click()
await delay(regularDelayMs)
}
for (let i = 0; i < 12; i++) {
await clickWordAndWait(words[i])
}
} catch (e) {
if (count > 2) {
throw e
} else {
await loadExtension(driver, extensionId)
await retypeSeedPhrase(words, true, count + 1)
}
}
}
it('can retype the seed phrase', async () => {
const words = seedPhrase.split(' ')
await retypeSeedPhrase(words)
const confirm = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
await confirm.click()
await delay(regularDelayMs)
})
it('clicks through the deposit modal', async () => {
const byBuyModal = By.css('span .modal')
const buyModal = await driver.wait(until.elementLocated(byBuyModal))
const closeModal = await findElement(driver, By.css('.page-container__header-close'))
await closeModal.click()
await driver.wait(until.stalenessOf(buyModal))
await delay(regularDelayMs)
})
it('switches to localhost', async () => {
const networkDropdown = await findElement(driver, By.css('.network-name'))
await networkDropdown.click()
await delay(regularDelayMs)
const [localhost] = await findElements(driver, By.xpath(`//span[contains(text(), 'Localhost')]`))
await localhost.click()
await delay(largeDelayMs * 2)
})
})
describe('Drizzle', () => {
it('should be able to detect our eth address', 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 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}$/))
})
})
})

View File

@ -286,7 +286,7 @@ describe('Using MetaMask with an existing account', function () {
await delay(regularDelayMs) await delay(regularDelayMs)
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]')) const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
const inputAmount = await findElement(driver, By.css('.currency-display__input')) const inputAmount = await findElement(driver, By.css('.unit-input__input'))
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
await inputAmount.sendKeys('1') await inputAmount.sendKeys('1')

View File

@ -271,6 +271,17 @@ describe('MetaMask', function () {
await driver.wait(until.stalenessOf(accountModal)) await driver.wait(until.stalenessOf(accountModal))
await delay(regularDelayMs) await delay(regularDelayMs)
}) })
it('show account details dropdown menu', async () => {
const {width, height} = await driver.manage().window().getSize()
driver.manage().window().setSize(320, 480)
await driver.findElement(By.css('div.menu-bar__open-in-browser')).click()
const options = await driver.findElements(By.css('div.menu.account-details-dropdown div.menu__item'))
assert.equal(options.length, 3) // HD Wallet type does not have to show the Remove Account option
await delay(regularDelayMs)
driver.manage().window().setSize(width, height)
})
}) })
describe('Log out an log back in', () => { describe('Log out an log back in', () => {
@ -372,7 +383,7 @@ describe('MetaMask', function () {
await delay(regularDelayMs) await delay(regularDelayMs)
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]')) const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
const inputAmount = await findElement(driver, By.css('.currency-display__input')) const inputAmount = await findElement(driver, By.css('.unit-input__input'))
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
await inputAmount.sendKeys('1') await inputAmount.sendKeys('1')
@ -651,7 +662,7 @@ describe('MetaMask', function () {
}) })
it('clicks on the Add Token button', async () => { it('clicks on the Add Token button', async () => {
const addToken = await driver.findElement(By.css('.wallet-view__add-token-button')) const addToken = await driver.findElement(By.xpath(`//div[contains(text(), 'Add Token')]`))
await addToken.click() await addToken.click()
await delay(regularDelayMs) await delay(regularDelayMs)
}) })
@ -691,7 +702,7 @@ describe('MetaMask', function () {
await delay(regularDelayMs) await delay(regularDelayMs)
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]')) const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
const inputAmount = await findElement(driver, By.css('.currency-display__input')) const inputAmount = await findElement(driver, By.css('.unit-input__input'))
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
await inputAmount.sendKeys('50') await inputAmount.sendKeys('50')
@ -823,8 +834,8 @@ describe('MetaMask', function () {
await save.click() await save.click()
await driver.wait(until.stalenessOf(gasModal)) await driver.wait(until.stalenessOf(gasModal))
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__eth')) const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__primary'))
assert.equal(await gasFeeInputs[0].getText(), '0.0006') assert.equal(await gasFeeInputs[0].getText(), '0.0006')
}) })
it('submits the transaction', async function () { it('submits the transaction', async function () {
@ -946,8 +957,8 @@ describe('MetaMask', function () {
await save.click() await save.click()
await driver.wait(until.stalenessOf(gasModal)) await driver.wait(until.stalenessOf(gasModal))
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__eth')) const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__primary'))
assert.equal(await gasFeeInputs[0].getText(), '0.0006') assert.equal(await gasFeeInputs[0].getText(), '0.0006')
}) })
it('submits the transaction', async function () { it('submits the transaction', async function () {
@ -991,7 +1002,7 @@ describe('MetaMask', function () {
describe('Add existing token using search', () => { describe('Add existing token using search', () => {
it('clicks on the Add Token button', async () => { it('clicks on the Add Token button', async () => {
const addToken = await findElement(driver, By.xpath(`//button[contains(text(), 'Add Token')]`)) const addToken = await findElement(driver, By.xpath(`//div[contains(text(), 'Add Token')]`))
await addToken.click() await addToken.click()
await delay(regularDelayMs) await delay(regularDelayMs)
}) })

View File

@ -6,5 +6,5 @@ set -o pipefail
export PATH="$PATH:./node_modules/.bin" export PATH="$PATH:./node_modules/.bin"
shell-parallel -s 'npm run ganache:start -- -b 2' -x 'sleep 5 && static-server test/e2e/beta/contract-test/ --port 8080' -x 'sleep 5 && mocha test/e2e/beta/metamask-beta-ui.spec' shell-parallel -s 'npm run ganache:start -- -b 2' -x 'sleep 5 && static-server test/e2e/beta/contract-test --port 8080' -x 'sleep 5 && mocha test/e2e/beta/metamask-beta-ui.spec'
shell-parallel -s 'npm run ganache:start -- -d -b 2' -x 'sleep 5 && static-server test/e2e/beta/contract-test/ --port 8080' -x 'sleep 5 && mocha test/e2e/beta/from-import-beta-ui.spec' shell-parallel -s 'npm run ganache:start -- -d -b 2' -x 'sleep 5 && mocha test/e2e/beta/from-import-beta-ui.spec'

20
test/e2e/beta/run-drizzle.sh Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -e
set -u
set -o pipefail
export PATH="$PATH:./node_modules/.bin"
npm run ganache:start -- -b 2 >> /dev/null 2>&1 &
sleep 5
cd test/e2e/beta/
rm -rf drizzle-test
mkdir drizzle-test && cd drizzle-test
npm install truffle
truffle unbox drizzle
echo "Deploying contracts for Drizzle test..."
truffle compile && truffle migrate
BROWSER=none npm start >> /dev/null 2>&1 &
cd ../../../../
mocha test/e2e/beta/drizzle.spec

View File

@ -1,140 +0,0 @@
const reactTriggerChange = require('react-trigger-change')
const {
timeout,
queryAsync,
findAsync,
} = require('../../lib/util')
QUnit.module('Add token flow')
QUnit.test('successful add token flow', (assert) => {
const done = assert.async()
runAddTokenFlowTest(assert)
.then(done)
.catch(err => {
assert.notOk(err, `Error was thrown: ${err.stack}`)
done()
})
})
async function runAddTokenFlowTest (assert, done) {
const selectState = await queryAsync($, 'select')
selectState.val('add token')
reactTriggerChange(selectState[0])
// Used to set values on TextField input component
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype, 'value'
).set
// Check that no tokens have been added
assert.ok($('.token-list-item').length === 0, 'no tokens added')
// Go to Add Token screen
let addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button')
assert.ok(addTokenButton[0], 'add token button present')
addTokenButton[0].click()
// Verify Add Token screen
let addTokenWrapper = await queryAsync($, '.page-container')
assert.ok(addTokenWrapper[0], 'add token wrapper renders')
let addTokenTitle = await queryAsync($, '.page-container__title')
assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct')
// Cancel Add Token
const cancelAddTokenButton = await queryAsync($, 'button.btn-default.btn--large.page-container__footer-button')
assert.ok(cancelAddTokenButton[0], 'cancel add token button present')
cancelAddTokenButton.click()
assert.ok($('.wallet-view')[0], 'cancelled and returned to account detail wallet view')
// Return to Add Token Screen
addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button')
assert.ok(addTokenButton[0], 'add token button present')
addTokenButton[0].click()
// Verify Add Token Screen
addTokenWrapper = await queryAsync($, '.page-container')
addTokenTitle = await queryAsync($, '.page-container__title')
assert.ok(addTokenWrapper[0], 'add token wrapper renders')
assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct')
// Search for token
const searchInput = (await findAsync(addTokenWrapper, '#search-tokens'))[0]
searchInput.focus()
await timeout(1000)
nativeInputValueSetter.call(searchInput, 'a')
searchInput.dispatchEvent(new Event('input', { bubbles: true}))
// Click token to add
const tokenWrapper = await queryAsync($, 'div.token-list__token')
assert.ok(tokenWrapper[0], 'token found')
const tokenImageProp = tokenWrapper.find('.token-list__token-icon').css('background-image')
const tokenImageUrl = tokenImageProp.slice(5, -2)
tokenWrapper[0].click()
// Click Next button
const nextButton = await queryAsync($, 'button.btn-primary.btn--large')
assert.equal(nextButton[0].textContent, 'Next', 'next button rendered')
nextButton[0].click()
// Confirm Add token
const confirmAddToken = await queryAsync($, '.confirm-add-token')
assert.ok(confirmAddToken[0], 'confirm add token rendered')
assert.ok($('button.btn-primary.btn--large')[0], 'confirm add token button found')
$('button.btn-primary.btn--large')[0].click()
// Verify added token image
let heroBalance = await queryAsync($, '.transaction-view-balance__balance-container')
assert.ok(heroBalance, 'rendered hero balance')
assert.ok(tokenImageUrl.indexOf(heroBalance.find('img').attr('src')) > -1, 'token added')
// Return to Add Token Screen
addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button')
assert.ok(addTokenButton[0], 'add token button present')
addTokenButton[0].click()
addTokenWrapper = await queryAsync($, '.page-container')
const addTokenTabs = await queryAsync($, '.page-container__tab')
assert.equal(addTokenTabs.length, 2, 'expected number of tabs')
assert.equal(addTokenTabs[1].textContent, 'Custom Token', 'Custom Token tab present')
assert.ok(addTokenTabs[1], 'add custom token tab present')
addTokenTabs[1].click()
await timeout(1000)
// Input token contract address
const customInput = (await findAsync(addTokenWrapper, '#custom-address'))[0]
customInput.focus()
await timeout(1000)
nativeInputValueSetter.call(customInput, '0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c')
customInput.dispatchEvent(new Event('input', { bubbles: true}))
// Click Next button
// nextButton = await queryAsync($, 'button.btn-primary--lg')
// assert.equal(nextButton[0].textContent, 'Next', 'next button rendered')
// nextButton[0].click()
// // Verify symbol length error since contract address won't return symbol
const errorMessage = await queryAsync($, '#custom-symbol-helper-text')
assert.ok(errorMessage[0], 'error rendered')
$('button.btn-default.btn--large')[0].click()
// await timeout(100000)
// Confirm Add token
// assert.equal(
// $('.page-container__subtitle')[0].textContent,
// 'Would you like to add these tokens?',
// 'confirm add token rendered'
// )
// assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found')
// $('button.btn-primary--lg')[0].click()
// Verify added token image
heroBalance = await queryAsync($, '.transaction-view-balance__balance-container')
assert.ok(heroBalance, 'rendered hero balance')
assert.ok(heroBalance.find('.identicon')[0], 'token added')
}

View File

@ -40,7 +40,7 @@ async function customizeGas (assert, price, limit, ethFee, usdFee) {
const sendGasField = await queryAsync($, '.send-v2__gas-fee-display') const sendGasField = await queryAsync($, '.send-v2__gas-fee-display')
assert.equal( assert.equal(
(await findAsync(sendGasField, '.currency-display__input-wrapper > input')).val(), (await findAsync(sendGasField, '.currency-display-component'))[0].textContent,
ethFee, ethFee,
'send gas field should show customized gas total' 'send gas field should show customized gas total'
) )
@ -94,12 +94,12 @@ async function runSendFlowTest (assert, done) {
sendToDropdownList.children()[2].click() sendToDropdownList.children()[2].click()
const sendToAccountAddress = sendToFieldInput.val() const sendToAccountAddress = sendToFieldInput.val()
assert.equal(sendToAccountAddress, '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', 'send to dropdown selects the correct address') assert.equal(sendToAccountAddress, '0x2f8D4a878cFA04A6E60D46362f5644DeAb66572D', 'send to dropdown selects the correct address')
const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(2)') const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(2)')
sendAmountField.find('.currency-display')[0].click() sendAmountField.find('.unit-input')[0].click()
const sendAmountFieldInput = await findAsync(sendAmountField, '.currency-display__input') const sendAmountFieldInput = await findAsync(sendAmountField, '.unit-input__input')
sendAmountFieldInput.val('5.1') sendAmountFieldInput.val('5.1')
reactTriggerChange(sendAmountField.find('input')[0]) reactTriggerChange(sendAmountField.find('input')[0])
@ -112,9 +112,9 @@ async function runSendFlowTest (assert, done) {
errorMessage = $('.send-v2__error') errorMessage = $('.send-v2__error')
assert.equal(errorMessage.length, 0, 'send should stop rendering amount error message after amount is corrected') assert.equal(errorMessage.length, 0, 'send should stop rendering amount error message after amount is corrected')
await customizeGas(assert, 0, 21000, '0', '$0.00 USD') await customizeGas(assert, 0, 21000, '0 ETH', '$0.00 USD')
await customizeGas(assert, 1, 21000, '0.000021', '$0.03 USD') await customizeGas(assert, 1, 21000, '0.000021 ETH', '$0.03 USD')
await customizeGas(assert, 500, 60000, '0.03', '$36.03 USD') await customizeGas(assert, 500, 60000, '0.03 ETH', '$36.03 USD')
const sendButton = await queryAsync($, 'button.btn-primary.btn--large.page-container__footer-button') const sendButton = await queryAsync($, 'button.btn-primary.btn--large.page-container__footer-button')
assert.equal(sendButton[0].textContent, 'Next', 'next button rendered') assert.equal(sendButton[0].textContent, 'Next', 'next button rendered')
@ -130,11 +130,11 @@ async function runSendFlowTest (assert, done) {
const confirmToName = (await queryAsync($, '.sender-to-recipient__name')).last() const confirmToName = (await queryAsync($, '.sender-to-recipient__name')).last()
assert.equal(confirmToName[0].textContent, 'Send Account 3', 'confirm screen should show correct to name') assert.equal(confirmToName[0].textContent, 'Send Account 3', 'confirm screen should show correct to name')
const confirmScreenRowFiats = await queryAsync($, '.confirm-detail-row__fiat') const confirmScreenRowFiats = await queryAsync($, '.confirm-detail-row__secondary')
const confirmScreenGas = confirmScreenRowFiats[0] const confirmScreenGas = confirmScreenRowFiats[0]
assert.equal(confirmScreenGas.textContent, '$3.60', 'confirm screen should show correct gas') assert.equal(confirmScreenGas.textContent, '$3.60', 'confirm screen should show correct gas')
const confirmScreenTotal = confirmScreenRowFiats[1] const confirmScreenTotal = confirmScreenRowFiats[1]
assert.equal(confirmScreenTotal.textContent, '$2,405.36', 'confirm screen should show correct total') assert.equal(confirmScreenTotal.textContent, '$2,405.37', 'confirm screen should show correct total')
const confirmScreenBackButton = await queryAsync($, '.confirm-page-container-header__back-button') const confirmScreenBackButton = await queryAsync($, '.confirm-page-container-header__back-button')
confirmScreenBackButton[0].click() confirmScreenBackButton[0].click()
@ -150,9 +150,9 @@ async function runSendFlowTest (assert, done) {
sendToFieldInputInEdit.val('0xd85a4b6a394794842887b8284293d69163007bbb') sendToFieldInputInEdit.val('0xd85a4b6a394794842887b8284293d69163007bbb')
const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(2)') const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(2)')
sendAmountFieldInEdit.find('.currency-display')[0].click() sendAmountFieldInEdit.find('.unit-input')[0].click()
const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('.currency-display__input') const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('.unit-input__input')
sendAmountFieldInputInEdit.val('1.0') sendAmountFieldInputInEdit.val('1.0')
reactTriggerChange(sendAmountFieldInputInEdit[0]) reactTriggerChange(sendAmountFieldInputInEdit[0])

View File

@ -479,5 +479,24 @@ describe('preferences controller', function () {
assert.equal(preferencesController.store.getState().seedWords, 'foo bar baz') assert.equal(preferencesController.store.getState().seedWords, 'foo bar baz')
}) })
}) })
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'])
})
it('should remove custom RPC url from state', function () {
preferencesController.addToFrequentRpcList('rpc_url')
assert.deepEqual(preferencesController.store.getState().frequentRpcList, ['rpc_url'])
preferencesController.removeFromFrequentRpcList('other_rpc_url')
preferencesController.removeFromFrequentRpcList('http://localhost:8545')
preferencesController.removeFromFrequentRpcList('rpc_url')
assert.deepEqual(preferencesController.store.getState().frequentRpcList, [])
})
})
}) })

View File

@ -158,9 +158,19 @@ describe('Transaction Controller', function () {
}) })
describe('#addUnapprovedTransaction', function () { describe('#addUnapprovedTransaction', function () {
const selectedAddress = '0x1678a085c290ebd122dc42cba69373b5953b831d'
let getSelectedAddress
beforeEach(function () {
getSelectedAddress = sinon.stub(txController, 'getSelectedAddress').returns(selectedAddress)
})
afterEach(function () {
getSelectedAddress.restore()
})
it('should add an unapproved transaction and return a valid txMeta', function (done) { it('should add an unapproved transaction and return a valid txMeta', function (done) {
txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d' }) txController.addUnapprovedTransaction({ from: selectedAddress })
.then((txMeta) => { .then((txMeta) => {
assert(('id' in txMeta), 'should have a id') assert(('id' in txMeta), 'should have a id')
assert(('time' in txMeta), 'should have a time stamp') assert(('time' in txMeta), 'should have a time stamp')
@ -180,25 +190,37 @@ describe('Transaction Controller', function () {
assert(txMetaFromEmit, 'txMeta is falsey') assert(txMetaFromEmit, 'txMeta is falsey')
done() done()
}) })
txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d' }) txController.addUnapprovedTransaction({ from: selectedAddress })
.catch(done) .catch(done)
}) })
it('should fail if recipient is public', function (done) { it('should fail if recipient is public', function (done) {
txController.networkStore = new ObservableStore(1) txController.networkStore = new ObservableStore(1)
txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d', to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' }) txController.addUnapprovedTransaction({ from: selectedAddress, to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' })
.catch((err) => { .catch((err) => {
if (err.message === 'Recipient is a public account') done() if (err.message === 'Recipient is a public account') done()
else done(err) else done(err)
}) })
}) })
it('should fail if the from address isn\'t the selected address', function (done) {
txController.addUnapprovedTransaction({from: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2'})
.then(function () {
assert.fail('transaction should not have been added')
done()
})
.catch(function () {
assert.ok('pass')
done()
})
})
it('should not fail if recipient is public but not on mainnet', function (done) { it('should not fail if recipient is public but not on mainnet', function (done) {
txController.once('newUnapprovedTx', (txMetaFromEmit) => { txController.once('newUnapprovedTx', (txMetaFromEmit) => {
assert(txMetaFromEmit, 'txMeta is falsey') assert(txMetaFromEmit, 'txMeta is falsey')
done() done()
}) })
txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d', to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' }) txController.addUnapprovedTransaction({ from: selectedAddress, to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' })
.catch(done) .catch(done)
}) })
}) })

View File

@ -1,44 +0,0 @@
const assert = require('assert')
const h = require('react-hyperscript')
const { createMockStore } = require('redux-test-utils')
const { shallowWithStore } = require('../../lib/render-helpers')
const BalanceComponent = require('../../../ui/app/components/balance-component')
const mockState = {
metamask: {
accounts: { abc: {} },
network: 1,
selectedAddress: 'abc',
},
}
describe('BalanceComponent', function () {
let balanceComponent
let store
let component
beforeEach(function () {
store = createMockStore(mockState)
component = shallowWithStore(h(BalanceComponent), store)
balanceComponent = component.dive()
})
it('shows token balance and convert to fiat value based on conversion rate', function () {
const formattedBalance = '1.23 ETH'
const tokenBalance = balanceComponent.instance().getTokenBalance(formattedBalance, false)
const fiatDisplayNumber = balanceComponent.instance().getFiatDisplayNumber(formattedBalance, 2)
assert.equal('1.23 ETH', tokenBalance)
assert.equal(2.46, fiatDisplayNumber)
})
it('shows only the token balance when conversion rate is not available', function () {
const formattedBalance = '1.23 ETH'
const tokenBalance = balanceComponent.instance().getTokenBalance(formattedBalance, false)
const fiatDisplayNumber = balanceComponent.instance().getFiatDisplayNumber(formattedBalance, 0)
assert.equal('1.23 ETH', tokenBalance)
assert.equal('N/A', fiatDisplayNumber)
})
})

File diff suppressed because it is too large Load Diff

View File

@ -305,6 +305,12 @@ var actions = {
updateFeatureFlags, updateFeatureFlags,
UPDATE_FEATURE_FLAGS: 'UPDATE_FEATURE_FLAGS', UPDATE_FEATURE_FLAGS: 'UPDATE_FEATURE_FLAGS',
// Preferences
setPreference,
updatePreferences,
UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
setUseETHAsPrimaryCurrencyPreference,
setMouseUserState, setMouseUserState,
SET_MOUSE_USER_STATE: 'SET_MOUSE_USER_STATE', SET_MOUSE_USER_STATE: 'SET_MOUSE_USER_STATE',
@ -1762,7 +1768,7 @@ function markNoticeRead (notice) {
background.markNoticeRead(notice, (err, notice) => { background.markNoticeRead(notice, (err, notice) => {
dispatch(actions.hideLoadingIndication()) dispatch(actions.hideLoadingIndication())
if (err) { if (err) {
dispatch(actions.displayWarning(err)) dispatch(actions.displayWarning(err.message))
return reject(err) return reject(err)
} }
@ -1852,7 +1858,7 @@ function setProviderType (type) {
background.setProviderType(type, (err, result) => { background.setProviderType(type, (err, result) => {
if (err) { if (err) {
log.error(err) log.error(err)
return dispatch(self.displayWarning('Had a problem changing networks!')) return dispatch(actions.displayWarning('Had a problem changing networks!'))
} }
dispatch(actions.updateProviderType(type)) dispatch(actions.updateProviderType(type))
dispatch(actions.setSelectedToken()) dispatch(actions.setSelectedToken())
@ -1874,7 +1880,7 @@ function setRpcTarget (newRpc) {
background.setCustomRpc(newRpc, (err, result) => { background.setCustomRpc(newRpc, (err, result) => {
if (err) { if (err) {
log.error(err) log.error(err)
return dispatch(self.displayWarning('Had a problem changing networks!')) return dispatch(actions.displayWarning('Had a problem changing networks!'))
} }
dispatch(actions.setSelectedToken()) dispatch(actions.setSelectedToken())
}) })
@ -2298,6 +2304,36 @@ function updateFeatureFlags (updatedFeatureFlags) {
} }
} }
function setPreference (preference, value) {
return dispatch => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.setPreference(preference, value, (err, updatedPreferences) => {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.updatePreferences(updatedPreferences))
resolve(updatedPreferences)
})
})
}
}
function updatePreferences (value) {
return {
type: actions.UPDATE_PREFERENCES,
value,
}
}
function setUseETHAsPrimaryCurrencyPreference (value) {
return setPreference('useETHAsPrimaryCurrency', value)
}
function setNetworkNonce (networkNonce) { function setNetworkNonce (networkNonce) {
return { return {
type: actions.SET_NETWORK_NONCE, type: actions.SET_NETWORK_NONCE,
@ -2309,6 +2345,10 @@ function updateNetworkNonce (address) {
return (dispatch) => { return (dispatch) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
global.ethQuery.getTransactionCount(address, (err, data) => { global.ethQuery.getTransactionCount(address, (err, data) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(setNetworkNonce(data)) dispatch(setNetworkNonce(data))
resolve(data) resolve(data)
}) })
@ -2396,7 +2436,7 @@ function setUseBlockie (val) {
function updateCurrentLocale (key) { function updateCurrentLocale (key) {
return (dispatch) => { return (dispatch) => {
dispatch(actions.showLoadingIndication()) dispatch(actions.showLoadingIndication())
fetchLocale(key) return fetchLocale(key)
.then((localeMessages) => { .then((localeMessages) => {
log.debug(`background.setCurrentLocale`) log.debug(`background.setCurrentLocale`)
background.setCurrentLocale(key, (err) => { background.setCurrentLocale(key, (err) => {

View File

@ -8,11 +8,11 @@ const h = require('react-hyperscript')
const actions = require('../../actions') const actions = require('../../actions')
const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu') const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
const Identicon = require('../identicon') const Identicon = require('../identicon')
const { formatBalance } = require('../../util')
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums') const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
const { getEnvironmentType } = require('../../../../app/scripts/lib/util') const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
const Tooltip = require('../tooltip') const Tooltip = require('../tooltip')
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
import { PRIMARY } from '../../constants/common'
const { const {
SETTINGS_ROUTE, SETTINGS_ROUTE,
@ -163,7 +163,6 @@ AccountMenu.prototype.renderAccounts = function () {
const isSelected = identity.address === selectedAddress const isSelected = identity.address === selectedAddress
const balanceValue = accounts[address] ? accounts[address].balance : '' const balanceValue = accounts[address] ? accounts[address].balance : ''
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...'
const simpleAddress = identity.address.substring(2).toLowerCase() const simpleAddress = identity.address.substring(2).toLowerCase()
const keyring = keyrings.find((kr) => { const keyring = keyrings.find((kr) => {
@ -189,7 +188,11 @@ AccountMenu.prototype.renderAccounts = function () {
h('div.account-menu__account-info', [ h('div.account-menu__account-info', [
h('div.account-menu__name', identity.name || ''), h('div.account-menu__name', identity.name || ''),
h('div.account-menu__balance', formattedBalance), h(UserPreferencedCurrencyDisplay, {
className: 'account-menu__balance',
value: balanceValue,
type: PRIMARY,
}),
]), ]),
this.renderKeyringType(keyring), this.renderKeyringType(keyring),

View File

@ -0,0 +1,34 @@
import PropTypes from 'prop-types'
import React, {PureComponent} from 'react'
export default class AddTokenButton extends PureComponent {
static contextTypes = {
t: PropTypes.func.isRequired,
}
static defaultProps = {
onClick: () => {},
}
static propTypes = {
onClick: PropTypes.func,
}
render () {
const { t } = this.context
const { onClick } = this.props
return (
<div className="add-token-button">
<h1 className="add-token-button__help-header">{t('missingYourTokens')}</h1>
<p className="add-token-button__help-desc">{t('clickToAdd', [t('addToken')])}</p>
<div
className="add-token-button__button"
onClick={onClick}
>
{t('addToken')}
</div>
</div>
)
}
}

View File

@ -0,0 +1 @@
export { default } from './add-token-button.component'

View File

@ -0,0 +1,26 @@
.add-token-button {
display: flex;
flex-direction: column;
color: lighten($scorpion, 25%);
width: 185px;
margin: 36px auto;
text-align: center;
&__help-header {
font-weight: bold;
font-size: 1rem;
}
&__help-desc {
font-size: 0.75rem;
margin-top: 1rem;
}
&__button {
font-size: 0.75rem;
margin: 1rem;
text-transform: uppercase;
color: $curious-blue;
cursor: pointer;
}
}

View File

@ -4,10 +4,11 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits const inherits = require('util').inherits
const TokenBalance = require('./token-balance') const TokenBalance = require('./token-balance')
const Identicon = require('./identicon') const Identicon = require('./identicon')
import CurrencyDisplay from './currency-display' import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../constants/common'
const { getAssetImages, conversionRateSelector, getCurrentCurrency} = require('../selectors') const { getAssetImages, conversionRateSelector, getCurrentCurrency} = require('../selectors')
const { formatBalance, generateBalanceObject } = require('../util') const { formatBalance } = require('../util')
module.exports = connect(mapStateToProps)(BalanceComponent) module.exports = connect(mapStateToProps)(BalanceComponent)
@ -65,7 +66,7 @@ BalanceComponent.prototype.renderTokenBalance = function () {
BalanceComponent.prototype.renderBalance = function () { BalanceComponent.prototype.renderBalance = function () {
const props = this.props const props = this.props
const { shorten, account } = props const { account } = props
const balanceValue = account && account.balance const balanceValue = account && account.balance
const needsParse = 'needsParse' in props ? props.needsParse : true const needsParse = 'needsParse' in props ? props.needsParse : true
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...' const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...'
@ -80,25 +81,20 @@ BalanceComponent.prototype.renderBalance = function () {
} }
return h('div.flex-column.balance-display', {}, [ return h('div.flex-column.balance-display', {}, [
h('div.token-amount', { h('div.token-amount', {}, h(UserPreferencedCurrencyDisplay, {
style: {},
}, this.getTokenBalance(formattedBalance, shorten)),
showFiat && h(CurrencyDisplay, {
value: balanceValue, value: balanceValue,
type: PRIMARY,
ethNumberOfDecimals: 3,
})),
showFiat && h(UserPreferencedCurrencyDisplay, {
value: balanceValue,
type: SECONDARY,
ethNumberOfDecimals: 3,
}), }),
]) ])
} }
BalanceComponent.prototype.getTokenBalance = function (formattedBalance, shorten) {
const balanceObj = generateBalanceObject(formattedBalance, shorten ? 1 : 3)
const balanceValue = shorten ? balanceObj.shortBalance : balanceObj.balance
const label = balanceObj.label
return `${balanceValue} ${label}`
}
BalanceComponent.prototype.getFiatDisplayNumber = function (formattedBalance, conversionRate) { BalanceComponent.prototype.getFiatDisplayNumber = function (formattedBalance, conversionRate) {
if (formattedBalance === 'None') return formattedBalance if (formattedBalance === 'None') return formattedBalance
if (conversionRate === 0) return 'N/A' if (conversionRate === 0) return 'N/A'

View File

@ -1,16 +1,19 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../../constants/common'
const ConfirmDetailRow = props => { const ConfirmDetailRow = props => {
const { const {
label, label,
fiatText, primaryText,
ethText, secondaryText,
onHeaderClick, onHeaderClick,
fiatTextColor, primaryValueTextColor,
headerText, headerText,
headerTextClassName, headerTextClassName,
value,
} = props } = props
return ( return (
@ -25,28 +28,57 @@ const ConfirmDetailRow = props => {
> >
{ headerText } { headerText }
</div> </div>
{
primaryText
? (
<div <div
className="confirm-detail-row__fiat" className="confirm-detail-row__primary"
style={{ color: fiatTextColor }} style={{ color: primaryValueTextColor }}
> >
{ fiatText } { primaryText }
</div> </div>
<div className="confirm-detail-row__eth"> ) : (
{ ethText } <UserPreferencedCurrencyDisplay
className="confirm-detail-row__primary"
type={PRIMARY}
value={value}
showEthLogo
ethLogoHeight="18"
style={{ color: primaryValueTextColor }}
hideLabel
/>
)
}
{
secondaryText
? (
<div className="confirm-detail-row__secondary">
{ secondaryText }
</div> </div>
) : (
<UserPreferencedCurrencyDisplay
className="confirm-detail-row__secondary"
type={SECONDARY}
value={value}
showEthLogo
hideLabel
/>
)
}
</div> </div>
</div> </div>
) )
} }
ConfirmDetailRow.propTypes = { ConfirmDetailRow.propTypes = {
label: PropTypes.string,
fiatText: PropTypes.string,
ethText: PropTypes.string,
fiatTextColor: PropTypes.string,
onHeaderClick: PropTypes.func,
headerText: PropTypes.string, headerText: PropTypes.string,
headerTextClassName: PropTypes.string, headerTextClassName: PropTypes.string,
label: PropTypes.string,
onHeaderClick: PropTypes.func,
primaryValueTextColor: PropTypes.string,
primaryText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
secondaryText: PropTypes.string,
value: PropTypes.string,
} }
export default ConfirmDetailRow export default ConfirmDetailRow

View File

@ -18,18 +18,14 @@
min-width: 0; min-width: 0;
} }
&__fiat { &__primary {
font-size: 1.5rem; font-size: 1.5rem;
white-space: nowrap; justify-content: flex-end;
overflow: hidden;
text-overflow: ellipsis;
} }
&__eth { &__secondary {
color: $oslo-gray; color: $oslo-gray;
white-space: nowrap; justify-content: flex-end;
overflow: hidden;
text-overflow: ellipsis;
} }
&__header-text { &__header-text {

View File

@ -12,17 +12,19 @@ describe('Confirm Detail Row Component', function () {
let wrapper let wrapper
beforeEach(() => { beforeEach(() => {
wrapper = shallow(<ConfirmDetailRow wrapper = shallow(
<ConfirmDetailRow
errorType={'mockErrorType'} errorType={'mockErrorType'}
label={'mockLabel'} label={'mockLabel'}
showError={false} showError={false}
fiatText = {'mockFiatText'} primaryText = {'mockFiatText'}
ethText = {'mockEthText'} secondaryText = {'mockEthText'}
fiatTextColor= {'mockColor'} primaryValueTextColor= {'mockColor'}
onHeaderClick= {propsMethodSpies.onHeaderClick} onHeaderClick= {propsMethodSpies.onHeaderClick}
headerText = {'mockHeaderText'} headerText = {'mockHeaderText'}
headerTextClassName = {'mockHeaderClass'} headerTextClassName = {'mockHeaderClass'}
/>) />
)
}) })
describe('render', () => { describe('render', () => {
@ -38,16 +40,16 @@ describe('Confirm Detail Row Component', function () {
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__header-text').childAt(0).text(), 'mockHeaderText') assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__header-text').childAt(0).text(), 'mockHeaderText')
}) })
it('should render the fiatText as a child of the confirm-detail-row__fiat', () => { it('should render the primaryText as a child of the confirm-detail-row__primary', () => {
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__fiat').childAt(0).text(), 'mockFiatText') assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__primary').childAt(0).text(), 'mockFiatText')
}) })
it('should render the ethText as a child of the confirm-detail-row__eth', () => { it('should render the ethText as a child of the confirm-detail-row__secondary', () => {
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__eth').childAt(0).text(), 'mockEthText') assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__secondary').childAt(0).text(), 'mockEthText')
}) })
it('should set the fiatTextColor on confirm-detail-row__fiat', () => { it('should set the fiatTextColor on confirm-detail-row__primary', () => {
assert.equal(wrapper.find('.confirm-detail-row__fiat').props().style.color, 'mockColor') assert.equal(wrapper.find('.confirm-detail-row__primary').props().style.color, 'mockColor')
}) })
it('should assure the confirm-detail-row__header-text classname is correct', () => { it('should assure the confirm-detail-row__header-text classname is correct', () => {
@ -58,7 +60,5 @@ describe('Confirm Detail Row Component', function () {
wrapper.find('.confirm-detail-row__header-text').props().onClick() wrapper.find('.confirm-detail-row__header-text').props().onClick()
assert.equal(assert.equal(propsMethodSpies.onHeaderClick.callCount, 1)) assert.equal(assert.equal(propsMethodSpies.onHeaderClick.callCount, 1))
}) })
}) })
}) })

View File

@ -17,9 +17,10 @@ export default class ConfirmPageContainerContent extends Component {
nonce: PropTypes.string, nonce: PropTypes.string,
assetImage: PropTypes.string, assetImage: PropTypes.string,
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
subtitleComponent: PropTypes.node,
summaryComponent: PropTypes.node, summaryComponent: PropTypes.node,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
titleComponent: PropTypes.func, titleComponent: PropTypes.node,
warning: PropTypes.string, warning: PropTypes.string,
} }
@ -54,7 +55,9 @@ export default class ConfirmPageContainerContent extends Component {
errorKey, errorKey,
errorMessage, errorMessage,
title, title,
titleComponent,
subtitle, subtitle,
subtitleComponent,
hideSubtitle, hideSubtitle,
identiconAddress, identiconAddress,
nonce, nonce,
@ -80,7 +83,9 @@ export default class ConfirmPageContainerContent extends Component {
})} })}
action={action} action={action}
title={title} title={title}
titleComponent={titleComponent}
subtitle={subtitle} subtitle={subtitle}
subtitleComponent={subtitleComponent}
hideSubtitle={hideSubtitle} hideSubtitle={hideSubtitle}
identiconAddress={identiconAddress} identiconAddress={identiconAddress}
nonce={nonce} nonce={nonce}

View File

@ -4,7 +4,18 @@ import classnames from 'classnames'
import Identicon from '../../../identicon' import Identicon from '../../../identicon'
const ConfirmPageContainerSummary = props => { const ConfirmPageContainerSummary = props => {
const { action, title, subtitle, hideSubtitle, className, identiconAddress, nonce, assetImage } = props const {
action,
title,
titleComponent,
subtitle,
subtitleComponent,
hideSubtitle,
className,
identiconAddress,
nonce,
assetImage,
} = props
return ( return (
<div className={classnames('confirm-page-container-summary', className)}> <div className={classnames('confirm-page-container-summary', className)}>
@ -32,12 +43,12 @@ const ConfirmPageContainerSummary = props => {
) )
} }
<div className="confirm-page-container-summary__title-text"> <div className="confirm-page-container-summary__title-text">
{ title } { titleComponent || title }
</div> </div>
</div> </div>
{ {
hideSubtitle || <div className="confirm-page-container-summary__subtitle"> hideSubtitle || <div className="confirm-page-container-summary__subtitle">
{ subtitle } { subtitleComponent || subtitle }
</div> </div>
} }
</div> </div>
@ -47,7 +58,9 @@ const ConfirmPageContainerSummary = props => {
ConfirmPageContainerSummary.propTypes = { ConfirmPageContainerSummary.propTypes = {
action: PropTypes.string, action: PropTypes.string,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
titleComponent: PropTypes.node,
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
subtitleComponent: PropTypes.node,
hideSubtitle: PropTypes.bool, hideSubtitle: PropTypes.bool,
className: PropTypes.string, className: PropTypes.string,
identiconAddress: PropTypes.string, identiconAddress: PropTypes.string,

View File

@ -16,8 +16,9 @@ export default class ConfirmPageContainer extends Component {
onEdit: PropTypes.func, onEdit: PropTypes.func,
showEdit: PropTypes.bool, showEdit: PropTypes.bool,
subtitle: PropTypes.string, subtitle: PropTypes.string,
subtitleComponent: PropTypes.node,
title: PropTypes.string, title: PropTypes.string,
titleComponent: PropTypes.func, titleComponent: PropTypes.node,
// Sender to Recipient // Sender to Recipient
fromAddress: PropTypes.string, fromAddress: PropTypes.string,
fromName: PropTypes.string, fromName: PropTypes.string,
@ -65,6 +66,7 @@ export default class ConfirmPageContainer extends Component {
title, title,
titleComponent, titleComponent,
subtitle, subtitle,
subtitleComponent,
hideSubtitle, hideSubtitle,
summaryComponent, summaryComponent,
detailsComponent, detailsComponent,
@ -101,6 +103,7 @@ export default class ConfirmPageContainer extends Component {
title={title} title={title}
titleComponent={titleComponent} titleComponent={titleComponent}
subtitle={subtitle} subtitle={subtitle}
subtitleComponent={subtitleComponent}
hideSubtitle={hideSubtitle} hideSubtitle={hideSubtitle}
summaryComponent={summaryComponent} summaryComponent={summaryComponent}
detailsComponent={detailsComponent} detailsComponent={detailsComponent}

View File

@ -1,5 +1,6 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import classnames from 'classnames'
import { ETH, GWEI } from '../../constants/common' import { ETH, GWEI } from '../../constants/common'
export default class CurrencyDisplay extends PureComponent { export default class CurrencyDisplay extends PureComponent {
@ -7,6 +8,8 @@ export default class CurrencyDisplay extends PureComponent {
className: PropTypes.string, className: PropTypes.string,
displayValue: PropTypes.string, displayValue: PropTypes.string,
prefix: PropTypes.string, prefix: PropTypes.string,
prefixComponent: PropTypes.node,
style: PropTypes.object,
// Used in container // Used in container
currency: PropTypes.oneOf([ETH]), currency: PropTypes.oneOf([ETH]),
denomination: PropTypes.oneOf([GWEI]), denomination: PropTypes.oneOf([GWEI]),
@ -16,15 +19,17 @@ export default class CurrencyDisplay extends PureComponent {
} }
render () { render () {
const { className, displayValue, prefix } = this.props const { className, displayValue, prefix, prefixComponent, style } = this.props
const text = `${prefix || ''}${displayValue}` const text = `${prefix || ''}${displayValue}`
return ( return (
<div <div
className={className} className={classnames('currency-display-component', className)}
style={style}
title={text} title={text}
> >
{ text } { prefixComponent}
<span className="currency-display-component__text">{ text }</span>
</div> </div>
) )
} }

View File

@ -2,10 +2,26 @@ import { connect } from 'react-redux'
import CurrencyDisplay from './currency-display.component' import CurrencyDisplay from './currency-display.component'
import { getValueFromWeiHex, formatCurrency } from '../../helpers/confirm-transaction/util' import { getValueFromWeiHex, formatCurrency } from '../../helpers/confirm-transaction/util'
const mapStateToProps = (state, ownProps) => { const mapStateToProps = state => {
const { value, numberOfDecimals = 2, currency, denomination, hideLabel } = ownProps
const { metamask: { currentCurrency, conversionRate } } = state const { metamask: { currentCurrency, conversionRate } } = state
return {
currentCurrency,
conversionRate,
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { currentCurrency, conversionRate, ...restStateProps } = stateProps
const {
value,
numberOfDecimals = 2,
currency,
denomination,
hideLabel,
...restOwnProps
} = ownProps
const toCurrency = currency || currentCurrency const toCurrency = currency || currentCurrency
const convertedValue = getValueFromWeiHex({ const convertedValue = getValueFromWeiHex({
value, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination, value, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination,
@ -14,8 +30,11 @@ const mapStateToProps = (state, ownProps) => {
const displayValue = hideLabel ? formattedValue : `${formattedValue} ${toCurrency.toUpperCase()}` const displayValue = hideLabel ? formattedValue : `${formattedValue} ${toCurrency.toUpperCase()}`
return { return {
...restStateProps,
...dispatchProps,
...restOwnProps,
displayValue, displayValue,
} }
} }
export default connect(mapStateToProps)(CurrencyDisplay) export default connect(mapStateToProps, null, mergeProps)(CurrencyDisplay)

View File

@ -0,0 +1,10 @@
.currency-display-component {
display: flex;
align-items: center;
&__text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}

View File

@ -1,12 +1,13 @@
import assert from 'assert' import assert from 'assert'
import proxyquire from 'proxyquire' import proxyquire from 'proxyquire'
let mapStateToProps let mapStateToProps, mergeProps
proxyquire('../currency-display.container.js', { proxyquire('../currency-display.container.js', {
'react-redux': { 'react-redux': {
connect: ms => { connect: (ms, md, mp) => {
mapStateToProps = ms mapStateToProps = ms
mergeProps = mp
return () => ({}) return () => ({})
}, },
}, },
@ -22,6 +23,20 @@ describe('CurrencyDisplay container', () => {
}, },
} }
assert.deepEqual(mapStateToProps(mockState), {
conversionRate: 280.45,
currentCurrency: 'usd',
})
})
})
describe('mergeProps()', () => {
it('should return the correct props', () => {
const mockStateProps = {
conversionRate: 280.45,
currentCurrency: 'usd',
}
const tests = [ const tests = [
{ {
props: { props: {
@ -98,7 +113,7 @@ describe('CurrencyDisplay container', () => {
] ]
tests.forEach(({ props, result }) => { tests.forEach(({ props, result }) => {
assert.deepEqual(mapStateToProps(mockState, props), result) assert.deepEqual(mergeProps(mockStateProps, {}, { ...props }), result)
}) })
}) })
}) })

View File

@ -0,0 +1,120 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import UnitInput from '../unit-input'
import CurrencyDisplay from '../currency-display'
import { getValueFromWeiHex, getWeiHexFromDecimalValue } from '../../helpers/conversions.util'
import { ETH } from '../../constants/common'
/**
* Component that allows user to enter currency values as a number, and props receive a converted
* hex value in WEI. props.value, used as a default or forced value, should be a hex value, which
* gets converted into a decimal value depending on the currency (ETH or Fiat).
*/
export default class CurrencyInput extends PureComponent {
static propTypes = {
conversionRate: PropTypes.number,
currentCurrency: PropTypes.string,
onChange: PropTypes.func,
onBlur: PropTypes.func,
suffix: PropTypes.string,
useFiat: PropTypes.bool,
value: PropTypes.string,
}
constructor (props) {
super(props)
const { value: hexValue } = props
const decimalValue = hexValue ? this.getDecimalValue(props) : 0
this.state = {
decimalValue,
hexValue,
}
}
componentDidUpdate (prevProps) {
const { value: prevPropsHexValue } = prevProps
const { value: propsHexValue } = this.props
const { hexValue: stateHexValue } = this.state
if (prevPropsHexValue !== propsHexValue && propsHexValue !== stateHexValue) {
const decimalValue = this.getDecimalValue(this.props)
this.setState({ hexValue: propsHexValue, decimalValue })
}
}
getDecimalValue (props) {
const { value: hexValue, useFiat, currentCurrency, conversionRate } = props
const decimalValueString = useFiat
? getValueFromWeiHex({
value: hexValue, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
})
: getValueFromWeiHex({
value: hexValue, toCurrency: ETH, numberOfDecimals: 6,
})
return Number(decimalValueString) || 0
}
handleChange = decimalValue => {
const { useFiat, currentCurrency: fromCurrency, conversionRate, onChange } = this.props
const hexValue = useFiat
? getWeiHexFromDecimalValue({
value: decimalValue, fromCurrency, conversionRate, invertConversionRate: true,
})
: getWeiHexFromDecimalValue({
value: decimalValue, fromCurrency: ETH, fromDenomination: ETH, conversionRate,
})
this.setState({ hexValue, decimalValue })
onChange(hexValue)
}
handleBlur = () => {
this.props.onBlur(this.state.hexValue)
}
renderConversionComponent () {
const { useFiat, currentCurrency } = this.props
const { hexValue } = this.state
let currency, numberOfDecimals
if (useFiat) {
// Display ETH
currency = ETH
numberOfDecimals = 6
} else {
// Display Fiat
currency = currentCurrency
numberOfDecimals = 2
}
return (
<CurrencyDisplay
className="currency-input__conversion-component"
currency={currency}
value={hexValue}
numberOfDecimals={numberOfDecimals}
/>
)
}
render () {
const { suffix, ...restProps } = this.props
const { decimalValue } = this.state
return (
<UnitInput
{...restProps}
suffix={suffix}
onChange={this.handleChange}
onBlur={this.handleBlur}
value={decimalValue}
>
{ this.renderConversionComponent() }
</UnitInput>
)
}
}

View File

@ -0,0 +1,27 @@
import { connect } from 'react-redux'
import CurrencyInput from './currency-input.component'
import { ETH } from '../../constants/common'
const mapStateToProps = state => {
const { metamask: { currentCurrency, conversionRate } } = state
return {
currentCurrency,
conversionRate,
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { currentCurrency } = stateProps
const { useFiat } = ownProps
const suffix = useFiat ? currentCurrency.toUpperCase() : ETH
return {
...stateProps,
...dispatchProps,
...ownProps,
suffix,
}
}
export default connect(mapStateToProps, null, mergeProps)(CurrencyInput)

View File

@ -0,0 +1 @@
export { default } from './currency-input.container'

View File

@ -0,0 +1,7 @@
.currency-input {
&__conversion-component {
font-size: 12px;
line-height: 12px;
padding-left: 1px;
}
}

View File

@ -0,0 +1,239 @@
import React from 'react'
import assert from 'assert'
import { shallow, mount } from 'enzyme'
import sinon from 'sinon'
import { Provider } from 'react-redux'
import configureMockStore from 'redux-mock-store'
import CurrencyInput from '../currency-input.component'
import UnitInput from '../../unit-input'
import CurrencyDisplay from '../../currency-display'
describe('CurrencyInput Component', () => {
describe('rendering', () => {
it('should render properly without a suffix', () => {
const wrapper = shallow(
<CurrencyInput />
)
assert.ok(wrapper)
assert.equal(wrapper.find(UnitInput).length, 1)
})
it('should render properly with a suffix', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
suffix="ETH"
/>
</Provider>
)
assert.ok(wrapper)
assert.equal(wrapper.find('.unit-input__suffix').length, 1)
assert.equal(wrapper.find('.unit-input__suffix').text(), 'ETH')
assert.equal(wrapper.find(CurrencyDisplay).length, 1)
})
it('should render properly with an ETH value', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
value="de0b6b3a7640000"
suffix="ETH"
currentCurrency="usd"
conversionRate={231.06}
/>
</Provider>
)
assert.ok(wrapper)
const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
assert.equal(currencyInputInstance.state.decimalValue, 1)
assert.equal(currencyInputInstance.state.hexValue, 'de0b6b3a7640000')
assert.equal(wrapper.find('.unit-input__suffix').length, 1)
assert.equal(wrapper.find('.unit-input__suffix').text(), 'ETH')
assert.equal(wrapper.find('.unit-input__input').props().value, '1')
assert.equal(wrapper.find('.currency-display-component').text(), '$231.06 USD')
})
it('should render properly with a fiat value', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
value="f602f2234d0ea"
suffix="USD"
useFiat
currentCurrency="usd"
conversionRate={231.06}
/>
</Provider>
)
assert.ok(wrapper)
const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
assert.equal(currencyInputInstance.state.decimalValue, 1)
assert.equal(currencyInputInstance.state.hexValue, 'f602f2234d0ea')
assert.equal(wrapper.find('.unit-input__suffix').length, 1)
assert.equal(wrapper.find('.unit-input__suffix').text(), 'USD')
assert.equal(wrapper.find('.unit-input__input').props().value, '1')
assert.equal(wrapper.find('.currency-display-component').text(), '0.004328 ETH')
})
})
describe('handling actions', () => {
const handleChangeSpy = sinon.spy()
const handleBlurSpy = sinon.spy()
afterEach(() => {
handleChangeSpy.resetHistory()
handleBlurSpy.resetHistory()
})
it('should call onChange and onBlur on input changes with the hex value for ETH', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
onChange={handleChangeSpy}
onBlur={handleBlurSpy}
suffix="ETH"
currentCurrency="usd"
conversionRate={231.06}
/>
</Provider>
)
assert.ok(wrapper)
assert.equal(handleChangeSpy.callCount, 0)
assert.equal(handleBlurSpy.callCount, 0)
const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
assert.equal(currencyInputInstance.state.decimalValue, 0)
assert.equal(currencyInputInstance.state.hexValue, undefined)
assert.equal(wrapper.find('.currency-display-component').text(), '$0.00 USD')
const input = wrapper.find('input')
assert.equal(input.props().value, 0)
input.simulate('change', { target: { value: 1 } })
assert.equal(handleChangeSpy.callCount, 1)
assert.ok(handleChangeSpy.calledWith('de0b6b3a7640000'))
assert.equal(wrapper.find('.currency-display-component').text(), '$231.06 USD')
assert.equal(currencyInputInstance.state.decimalValue, 1)
assert.equal(currencyInputInstance.state.hexValue, 'de0b6b3a7640000')
assert.equal(handleBlurSpy.callCount, 0)
input.simulate('blur')
assert.equal(handleBlurSpy.callCount, 1)
assert.ok(handleBlurSpy.calledWith('de0b6b3a7640000'))
})
it('should call onChange and onBlur on input changes with the hex value for fiat', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
onChange={handleChangeSpy}
onBlur={handleBlurSpy}
suffix="USD"
currentCurrency="usd"
conversionRate={231.06}
useFiat
/>
</Provider>
)
assert.ok(wrapper)
assert.equal(handleChangeSpy.callCount, 0)
assert.equal(handleBlurSpy.callCount, 0)
const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
assert.equal(currencyInputInstance.state.decimalValue, 0)
assert.equal(currencyInputInstance.state.hexValue, undefined)
assert.equal(wrapper.find('.currency-display-component').text(), '0 ETH')
const input = wrapper.find('input')
assert.equal(input.props().value, 0)
input.simulate('change', { target: { value: 1 } })
assert.equal(handleChangeSpy.callCount, 1)
assert.ok(handleChangeSpy.calledWith('f602f2234d0ea'))
assert.equal(wrapper.find('.currency-display-component').text(), '0.004328 ETH')
assert.equal(currencyInputInstance.state.decimalValue, 1)
assert.equal(currencyInputInstance.state.hexValue, 'f602f2234d0ea')
assert.equal(handleBlurSpy.callCount, 0)
input.simulate('blur')
assert.equal(handleBlurSpy.callCount, 1)
assert.ok(handleBlurSpy.calledWith('f602f2234d0ea'))
})
it('should change the state and pass in a new decimalValue when props.value changes', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = shallow(
<Provider store={store}>
<CurrencyInput
onChange={handleChangeSpy}
onBlur={handleBlurSpy}
suffix="USD"
currentCurrency="usd"
conversionRate={231.06}
useFiat
/>
</Provider>
)
assert.ok(wrapper)
const currencyInputInstance = wrapper.find(CurrencyInput).dive()
assert.equal(currencyInputInstance.state('decimalValue'), 0)
assert.equal(currencyInputInstance.state('hexValue'), undefined)
assert.equal(currencyInputInstance.find(UnitInput).props().value, 0)
currencyInputInstance.setProps({ value: '1ec05e43e72400' })
currencyInputInstance.update()
assert.equal(currencyInputInstance.state('decimalValue'), 2)
assert.equal(currencyInputInstance.state('hexValue'), '1ec05e43e72400')
assert.equal(currencyInputInstance.find(UnitInput).props().value, 2)
})
})
})

View File

@ -0,0 +1,55 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
let mapStateToProps, mergeProps
proxyquire('../currency-input.container.js', {
'react-redux': {
connect: (ms, md, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
},
},
})
describe('CurrencyInput container', () => {
describe('mapStateToProps()', () => {
it('should return the correct props', () => {
const mockState = {
metamask: {
conversionRate: 280.45,
currentCurrency: 'usd',
},
}
assert.deepEqual(mapStateToProps(mockState), {
conversionRate: 280.45,
currentCurrency: 'usd',
})
})
})
describe('mergeProps()', () => {
it('should return the correct props', () => {
const mockStateProps = {
conversionRate: 280.45,
currentCurrency: 'usd',
}
const mockDispatchProps = {}
assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, { useFiat: true }), {
conversionRate: 280.45,
currentCurrency: 'usd',
useFiat: true,
suffix: 'USD',
})
assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, {}), {
conversionRate: 280.45,
currentCurrency: 'usd',
suffix: 'ETH',
})
})
})
})

View File

@ -0,0 +1,109 @@
const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
const { getSelectedIdentity } = require('../../selectors')
const genAccountLink = require('../../../lib/account-link.js')
const { Menu, Item, CloseArea } = require('./components/menu')
AccountDetailsDropdown.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDetailsDropdown)
function mapStateToProps (state) {
return {
selectedIdentity: getSelectedIdentity(state),
network: state.metamask.network,
keyrings: state.metamask.keyrings,
}
}
function mapDispatchToProps (dispatch) {
return {
showAccountDetailModal: () => {
dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' }))
},
viewOnEtherscan: (address, network) => {
global.platform.openWindow({ url: genAccountLink(address, network) })
},
showRemoveAccountConfirmationModal: (identity) => {
return dispatch(actions.showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity }))
},
}
}
inherits(AccountDetailsDropdown, Component)
function AccountDetailsDropdown () {
Component.call(this)
this.onClose = this.onClose.bind(this)
}
AccountDetailsDropdown.prototype.onClose = function (e) {
e.stopPropagation()
this.props.onClose()
}
AccountDetailsDropdown.prototype.render = function () {
const {
selectedIdentity,
network,
keyrings,
showAccountDetailModal,
viewOnEtherscan,
showRemoveAccountConfirmationModal } = this.props
const address = selectedIdentity.address
const keyring = keyrings.find((kr) => {
return kr.accounts.includes(address)
})
const isRemovable = keyring.type !== 'HD Key Tree'
return h(Menu, { className: 'account-details-dropdown', isShowing: true }, [
h(CloseArea, {
onClick: this.onClose,
}),
h(Item, {
onClick: (e) => {
e.stopPropagation()
global.platform.openExtensionInBrowser()
this.props.onClose()
},
text: this.context.t('expandView'),
icon: h(`img`, { src: 'images/expand.svg', style: { height: '15px' } }),
}),
h(Item, {
onClick: (e) => {
e.stopPropagation()
showAccountDetailModal()
this.props.onClose()
},
text: this.context.t('accountDetails'),
icon: h(`img`, { src: 'images/info.svg', style: { height: '15px' } }),
}),
h(Item, {
onClick: (e) => {
e.stopPropagation()
viewOnEtherscan(address, network)
this.props.onClose()
},
text: this.context.t('viewOnEtherscan'),
icon: h(`img`, { src: 'images/open-etherscan.svg', style: { height: '15px' } }),
}),
isRemovable ? h(Item, {
onClick: (e) => {
e.stopPropagation()
showRemoveAccountConfirmationModal(selectedIdentity)
this.props.onClose()
},
text: this.context.t('removeAccount'),
icon: h(`img`, { src: 'images/hide.svg', style: { height: '15px' } }),
}) : null,
])
}

View File

@ -1,11 +1,17 @@
@import './app-header/index'; @import './app-header/index';
@import './add-token-button/index';
@import './button-group/index'; @import './button-group/index';
@import './card/index'; @import './card/index';
@import './confirm-page-container/index'; @import './confirm-page-container/index';
@import './currency-input/index';
@import './currency-display/index';
@import './error-message/index'; @import './error-message/index';
@import './export-text-container/index'; @import './export-text-container/index';
@ -49,3 +55,5 @@
@import './app-header/index'; @import './app-header/index';
@import './sidebars/index'; @import './sidebars/index';
@import './unit-input/index';

View File

@ -2,6 +2,7 @@ import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import Tooltip from '../tooltip' import Tooltip from '../tooltip'
import SelectedAccount from '../selected-account' import SelectedAccount from '../selected-account'
import AccountDetailsDropdown from '../dropdowns/account-details-dropdown.js'
export default class MenuBar extends PureComponent { export default class MenuBar extends PureComponent {
static contextTypes = { static contextTypes = {
@ -15,9 +16,12 @@ export default class MenuBar extends PureComponent {
showSidebar: PropTypes.func, showSidebar: PropTypes.func,
} }
state = { accountDetailsMenuOpen: false }
render () { render () {
const { t } = this.context const { t } = this.context
const { isMascara, sidebarOpen, hideSidebar, showSidebar } = this.props const { isMascara, sidebarOpen, hideSidebar, showSidebar } = this.props
const { accountDetailsMenuOpen } = this.state
return ( return (
<div className="menu-bar"> <div className="menu-bar">
@ -34,18 +38,25 @@ export default class MenuBar extends PureComponent {
{ {
!isMascara && ( !isMascara && (
<Tooltip <Tooltip
title={t('openInTab')} title={t('accountOptions')}
position="bottom" position="bottom"
> >
<div <div
className="menu-bar__open-in-browser" className="fa fa-ellipsis-h fa-lg menu-bar__open-in-browser"
onClick={() => global.platform.openExtensionInBrowser()} onClick={() => this.setState({ accountDetailsMenuOpen: true })}
> >
<img src="images/popout.svg" />
</div> </div>
</Tooltip> </Tooltip>
) )
} }
{
accountDetailsMenuOpen && (
<AccountDetailsDropdown
className="menu-bar__account-details-dropdown"
onClose={() => this.setState({ accountDetailsMenuOpen: false })}
/>
)
}
</div> </div>
) )
} }

View File

@ -1,7 +1,7 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import CurrencyDisplay from '../../../currency-display' import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display'
import { ETH } from '../../../../constants/common' import { PRIMARY, SECONDARY } from '../../../../constants/common'
export default class CancelTransaction extends PureComponent { export default class CancelTransaction extends PureComponent {
static propTypes = { static propTypes = {
@ -13,15 +13,15 @@ export default class CancelTransaction extends PureComponent {
return ( return (
<div className="cancel-transaction-gas-fee"> <div className="cancel-transaction-gas-fee">
<CurrencyDisplay <UserPreferencedCurrencyDisplay
className="cancel-transaction-gas-fee__eth" className="cancel-transaction-gas-fee__eth"
currency={ETH}
value={value} value={value}
numberOfDecimals={6} type={PRIMARY}
/> />
<CurrencyDisplay <UserPreferencedCurrencyDisplay
className="cancel-transaction-gas-fee__fiat" className="cancel-transaction-gas-fee__fiat"
value={value} value={value}
type={SECONDARY}
/> />
</div> </div>
) )

View File

@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert' import assert from 'assert'
import { shallow } from 'enzyme' import { shallow } from 'enzyme'
import CancelTransactionGasFee from '../cancel-transaction-gas-fee.component' import CancelTransactionGasFee from '../cancel-transaction-gas-fee.component'
import CurrencyDisplay from '../../../../currency-display' import UserPreferencedCurrencyDisplay from '../../../../user-preferenced-currency-display'
describe('CancelTransactionGasFee Component', () => { describe('CancelTransactionGasFee Component', () => {
it('should render', () => { it('should render', () => {
@ -13,12 +13,11 @@ describe('CancelTransactionGasFee Component', () => {
) )
assert.ok(wrapper) assert.ok(wrapper)
assert.equal(wrapper.find(CurrencyDisplay).length, 2) assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
const ethDisplay = wrapper.find(CurrencyDisplay).at(0) const ethDisplay = wrapper.find(UserPreferencedCurrencyDisplay).at(0)
const fiatDisplay = wrapper.find(CurrencyDisplay).at(1) const fiatDisplay = wrapper.find(UserPreferencedCurrencyDisplay).at(1)
assert.equal(ethDisplay.props().value, '0x3b9aca00') assert.equal(ethDisplay.props().value, '0x3b9aca00')
assert.equal(ethDisplay.props().currency, 'ETH')
assert.equal(ethDisplay.props().className, 'cancel-transaction-gas-fee__eth') assert.equal(ethDisplay.props().className, 'cancel-transaction-gas-fee__eth')
assert.equal(fiatDisplay.props().value, '0x3b9aca00') assert.equal(fiatDisplay.props().value, '0x3b9aca00')

View File

@ -78,7 +78,7 @@ export default class ConfirmRemoveAccount extends Component {
<a <a
className="confirm-remove-account__link" className="confirm-remove-account__link"
rel="noopener noreferrer" rel="noopener noreferrer"
target="_blank" href="https://consensys.zendesk.com/hc/en-us/articles/360004180111-What-are-imported-accounts-New-UI-"> target="_blank" href="https://metamask.zendesk.com/hc/en-us/articles/360015289932">
{ t('learnMore') } { t('learnMore') }
</a> </a>
</div> </div>

View File

@ -15,7 +15,7 @@ export default class TokenListPlaceholder extends Component {
</div> </div>
<a <a
className="token-list-placeholder__link" className="token-list-placeholder__link"
href="https://consensys.zendesk.com/hc/en-us/articles/360004135092" href="https://metamask.zendesk.com/hc/en-us/articles/360015489031"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >

View File

@ -1,12 +1,15 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import ConfirmTransactionBase from '../confirm-transaction-base' import ConfirmTransactionBase from '../confirm-transaction-base'
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
import { import {
formatCurrency, formatCurrency,
convertTokenToFiat, convertTokenToFiat,
addFiat, addFiat,
roundExponential, roundExponential,
} from '../../../helpers/confirm-transaction/util' } from '../../../helpers/confirm-transaction/util'
import { getWeiHexFromDecimalValue } from '../../../helpers/conversions.util'
import { ETH, PRIMARY } from '../../../constants/common'
export default class ConfirmTokenTransactionBase extends Component { export default class ConfirmTokenTransactionBase extends Component {
static contextTypes = { static contextTypes = {
@ -36,19 +39,48 @@ export default class ConfirmTokenTransactionBase extends Component {
}) })
} }
getSubtitle () { renderSubtitleComponent () {
const { currentCurrency, contractExchangeRate } = this.props const { contractExchangeRate, tokenAmount } = this.props
if (typeof contractExchangeRate === 'undefined') { const decimalEthValue = (tokenAmount * contractExchangeRate) || 0
return this.context.t('noConversionRateAvailable') const hexWeiValue = getWeiHexFromDecimalValue({
} else { value: decimalEthValue,
const fiatTransactionAmount = this.getFiatTransactionAmount() fromCurrency: ETH,
const roundedFiatTransactionAmount = roundExponential(fiatTransactionAmount) fromDenomination: ETH,
return formatCurrency(roundedFiatTransactionAmount, currentCurrency) })
}
return typeof contractExchangeRate === 'undefined'
? (
<span>
{ this.context.t('noConversionRateAvailable') }
</span>
) : (
<UserPreferencedCurrencyDisplay
value={hexWeiValue}
type={PRIMARY}
showEthLogo
hideLabel
/>
)
} }
getFiatTotalTextOverride () { renderPrimaryTotalTextOverride () {
const { tokenAmount, tokenSymbol, ethTransactionTotal } = this.props
const tokensText = `${tokenAmount} ${tokenSymbol}`
return (
<div>
<span>{ `${tokensText} + ` }</span>
<img
src="/images/eth.svg"
height="18"
/>
<span>{ ethTransactionTotal }</span>
</div>
)
}
getSecondaryTotalTextOverride () {
const { fiatTransactionTotal, currentCurrency, contractExchangeRate } = this.props const { fiatTransactionTotal, currentCurrency, contractExchangeRate } = this.props
if (typeof contractExchangeRate === 'undefined') { if (typeof contractExchangeRate === 'undefined') {
@ -67,7 +99,6 @@ export default class ConfirmTokenTransactionBase extends Component {
tokenAddress, tokenAddress,
tokenSymbol, tokenSymbol,
tokenAmount, tokenAmount,
ethTransactionTotal,
...restProps ...restProps
} = this.props } = this.props
@ -78,9 +109,9 @@ export default class ConfirmTokenTransactionBase extends Component {
toAddress={toAddress} toAddress={toAddress}
identiconAddress={tokenAddress} identiconAddress={tokenAddress}
title={tokensText} title={tokensText}
subtitle={this.getSubtitle()} subtitleComponent={this.renderSubtitleComponent()}
ethTotalTextOverride={`${tokensText} + \u2666 ${ethTransactionTotal}`} primaryTotalTextOverride={this.renderPrimaryTotalTextOverride()}
fiatTotalTextOverride={this.getFiatTotalTextOverride()} secondaryTotalTextOverride={this.getSecondaryTotalTextOverride()}
{...restProps} {...restProps}
/> />
) )

View File

@ -1,7 +1,6 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import ConfirmPageContainer, { ConfirmDetailRow } from '../../confirm-page-container' import ConfirmPageContainer, { ConfirmDetailRow } from '../../confirm-page-container'
import { formatCurrency } from '../../../helpers/confirm-transaction/util'
import { isBalanceSufficient } from '../../send/send.utils' import { isBalanceSufficient } from '../../send/send.utils'
import { DEFAULT_ROUTE } from '../../../routes' import { DEFAULT_ROUTE } from '../../../routes'
import { import {
@ -9,6 +8,8 @@ import {
TRANSACTION_ERROR_KEY, TRANSACTION_ERROR_KEY,
} from '../../../constants/error-keys' } from '../../../constants/error-keys'
import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../../constants/transactions' import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../../constants/transactions'
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../../constants/common'
export default class ConfirmTransactionBase extends Component { export default class ConfirmTransactionBase extends Component {
static contextTypes = { static contextTypes = {
@ -36,7 +37,9 @@ export default class ConfirmTransactionBase extends Component {
fiatTransactionTotal: PropTypes.string, fiatTransactionTotal: PropTypes.string,
fromAddress: PropTypes.string, fromAddress: PropTypes.string,
fromName: PropTypes.string, fromName: PropTypes.string,
hexGasTotal: PropTypes.string, hexTransactionAmount: PropTypes.string,
hexTransactionFee: PropTypes.string,
hexTransactionTotal: PropTypes.string,
isTxReprice: PropTypes.bool, isTxReprice: PropTypes.bool,
methodData: PropTypes.object, methodData: PropTypes.object,
nonce: PropTypes.string, nonce: PropTypes.string,
@ -59,8 +62,8 @@ export default class ConfirmTransactionBase extends Component {
detailsComponent: PropTypes.node, detailsComponent: PropTypes.node,
errorKey: PropTypes.string, errorKey: PropTypes.string,
errorMessage: PropTypes.string, errorMessage: PropTypes.string,
ethTotalTextOverride: PropTypes.string, primaryTotalTextOverride: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
fiatTotalTextOverride: PropTypes.string, secondaryTotalTextOverride: PropTypes.string,
hideData: PropTypes.bool, hideData: PropTypes.bool,
hideDetails: PropTypes.bool, hideDetails: PropTypes.bool,
hideSubtitle: PropTypes.bool, hideSubtitle: PropTypes.bool,
@ -70,8 +73,10 @@ export default class ConfirmTransactionBase extends Component {
onEditGas: PropTypes.func, onEditGas: PropTypes.func,
onSubmit: PropTypes.func, onSubmit: PropTypes.func,
subtitle: PropTypes.string, subtitle: PropTypes.string,
subtitleComponent: PropTypes.node,
summaryComponent: PropTypes.node, summaryComponent: PropTypes.node,
title: PropTypes.string, title: PropTypes.string,
titleComponent: PropTypes.node,
valid: PropTypes.bool, valid: PropTypes.bool,
warning: PropTypes.string, warning: PropTypes.string,
} }
@ -105,7 +110,7 @@ export default class ConfirmTransactionBase extends Component {
const { const {
balance, balance,
conversionRate, conversionRate,
hexGasTotal, hexTransactionFee,
txData: { txData: {
simulationFails, simulationFails,
txParams: { txParams: {
@ -116,7 +121,7 @@ export default class ConfirmTransactionBase extends Component {
const insufficientBalance = balance && !isBalanceSufficient({ const insufficientBalance = balance && !isBalanceSufficient({
amount, amount,
gasTotal: hexGasTotal || '0x0', gasTotal: hexTransactionFee || '0x0',
balance, balance,
conversionRate, conversionRate,
}) })
@ -153,13 +158,10 @@ export default class ConfirmTransactionBase extends Component {
renderDetails () { renderDetails () {
const { const {
detailsComponent, detailsComponent,
fiatTransactionFee, primaryTotalTextOverride,
ethTransactionFee, secondaryTotalTextOverride,
currentCurrency, hexTransactionFee,
fiatTransactionTotal, hexTransactionTotal,
ethTransactionTotal,
fiatTotalTextOverride,
ethTotalTextOverride,
hideDetails, hideDetails,
} = this.props } = this.props
@ -167,16 +169,13 @@ export default class ConfirmTransactionBase extends Component {
return null return null
} }
const formattedCurrency = formatCurrency(fiatTransactionTotal, currentCurrency)
return ( return (
detailsComponent || ( detailsComponent || (
<div className="confirm-page-container-content__details"> <div className="confirm-page-container-content__details">
<div className="confirm-page-container-content__gas-fee"> <div className="confirm-page-container-content__gas-fee">
<ConfirmDetailRow <ConfirmDetailRow
label="Gas Fee" label="Gas Fee"
fiatText={formatCurrency(fiatTransactionFee, currentCurrency)} value={hexTransactionFee}
ethText={`\u2666 ${ethTransactionFee}`}
headerText="Edit" headerText="Edit"
headerTextClassName="confirm-detail-row__header-text--edit" headerTextClassName="confirm-detail-row__header-text--edit"
onHeaderClick={() => this.handleEditGas()} onHeaderClick={() => this.handleEditGas()}
@ -185,11 +184,12 @@ export default class ConfirmTransactionBase extends Component {
<div> <div>
<ConfirmDetailRow <ConfirmDetailRow
label="Total" label="Total"
fiatText={fiatTotalTextOverride || formattedCurrency} value={hexTransactionTotal}
ethText={ethTotalTextOverride || `\u2666 ${ethTransactionTotal}`} primaryText={primaryTotalTextOverride}
secondaryText={secondaryTotalTextOverride}
headerText="Amount + Gas Fee" headerText="Amount + Gas Fee"
headerTextClassName="confirm-detail-row__header-text--total" headerTextClassName="confirm-detail-row__header-text--total"
fiatTextColor="#2f9ae0" primaryValueTextColor="#2f9ae0"
/> />
</div> </div>
</div> </div>
@ -311,6 +311,43 @@ export default class ConfirmTransactionBase extends Component {
} }
} }
renderTitleComponent () {
const { title, titleComponent, hexTransactionAmount } = this.props
// Title string passed in by props takes priority
if (title) {
return null
}
return titleComponent || (
<UserPreferencedCurrencyDisplay
value={hexTransactionAmount}
type={PRIMARY}
showEthLogo
ethLogoHeight="26"
hideLabel
/>
)
}
renderSubtitleComponent () {
const { subtitle, subtitleComponent, hexTransactionAmount } = this.props
// Subtitle string passed in by props takes priority
if (subtitle) {
return null
}
return subtitleComponent || (
<UserPreferencedCurrencyDisplay
value={hexTransactionAmount}
type={SECONDARY}
showEthLogo
hideLabel
/>
)
}
render () { render () {
const { const {
isTxReprice, isTxReprice,
@ -319,12 +356,9 @@ export default class ConfirmTransactionBase extends Component {
toName, toName,
toAddress, toAddress,
methodData, methodData,
ethTransactionAmount,
fiatTransactionAmount,
valid: propsValid = true, valid: propsValid = true,
errorMessage, errorMessage,
errorKey: propsErrorKey, errorKey: propsErrorKey,
currentCurrency,
action, action,
title, title,
subtitle, subtitle,
@ -341,7 +375,6 @@ export default class ConfirmTransactionBase extends Component {
const { submitting, submitError } = this.state const { submitting, submitError } = this.state
const { name } = methodData const { name } = methodData
const fiatConvertedAmount = formatCurrency(fiatTransactionAmount, currentCurrency)
const { valid, errorKey } = this.getErrorKey() const { valid, errorKey } = this.getErrorKey()
return ( return (
@ -352,8 +385,10 @@ export default class ConfirmTransactionBase extends Component {
toAddress={toAddress} toAddress={toAddress}
showEdit={onEdit && !isTxReprice} showEdit={onEdit && !isTxReprice}
action={action || name || this.context.t('unknownFunction')} action={action || name || this.context.t('unknownFunction')}
title={title || `${fiatConvertedAmount} ${currentCurrency.toUpperCase()}`} title={title}
subtitle={subtitle || `\u2666 ${ethTransactionAmount}`} titleComponent={this.renderTitleComponent()}
subtitle={subtitle}
subtitleComponent={this.renderSubtitleComponent()}
hideSubtitle={hideSubtitle} hideSubtitle={hideSubtitle}
summaryComponent={summaryComponent} summaryComponent={summaryComponent}
detailsComponent={this.renderDetails()} detailsComponent={this.renderDetails()}

View File

@ -36,7 +36,9 @@ const mapStateToProps = (state, props) => {
fiatTransactionAmount, fiatTransactionAmount,
fiatTransactionFee, fiatTransactionFee,
fiatTransactionTotal, fiatTransactionTotal,
hexGasTotal, hexTransactionAmount,
hexTransactionFee,
hexTransactionTotal,
tokenData, tokenData,
methodData, methodData,
txData, txData,
@ -87,7 +89,9 @@ const mapStateToProps = (state, props) => {
fiatTransactionAmount, fiatTransactionAmount,
fiatTransactionFee, fiatTransactionFee,
fiatTransactionTotal, fiatTransactionTotal,
hexGasTotal, hexTransactionAmount,
hexTransactionFee,
hexTransactionTotal,
txData, txData,
tokenData, tokenData,
methodData, methodData,

View File

@ -46,7 +46,7 @@ AccountImportSubview.prototype.render = function () {
}, },
onClick: () => { onClick: () => {
global.platform.openWindow({ global.platform.openWindow({
url: 'https://consensys.zendesk.com/hc/en-us/articles/360004180111-What-are-imported-accounts-New-UI', url: 'https://metamask.zendesk.com/hc/en-us/articles/360015289932',
}) })
}, },
}, this.context.t('here')), }, this.context.t('here')),

View File

@ -48,4 +48,22 @@
border-color: $ecstasy; border-color: $ecstasy;
} }
} }
&__radio-buttons {
display: flex;
align-items: center;
}
&__radio-button {
display: flex;
align-items: center;
&:not(:last-child) {
margin-right: 16px;
}
}
&__radio-label {
padding-left: 4px;
}
} }

View File

@ -55,6 +55,8 @@ export default class SettingsTab extends PureComponent {
sendHexData: PropTypes.bool, sendHexData: PropTypes.bool,
currentCurrency: PropTypes.string, currentCurrency: PropTypes.string,
conversionDate: PropTypes.number, conversionDate: PropTypes.number,
useETHAsPrimaryCurrency: PropTypes.bool,
setUseETHAsPrimaryCurrencyPreference: PropTypes.func,
} }
state = { state = {
@ -339,6 +341,56 @@ export default class SettingsTab extends PureComponent {
) )
} }
renderUseEthAsPrimaryCurrency () {
const { t } = this.context
const { useETHAsPrimaryCurrency, setUseETHAsPrimaryCurrencyPreference } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('primaryCurrencySetting') }</span>
<div className="settings-page__content-description">
{ t('primaryCurrencySettingDescription') }
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<div className="settings-tab__radio-buttons">
<div className="settings-tab__radio-button">
<input
type="radio"
id="eth-primary-currency"
onChange={() => setUseETHAsPrimaryCurrencyPreference(true)}
checked={Boolean(useETHAsPrimaryCurrency)}
/>
<label
htmlFor="eth-primary-currency"
className="settings-tab__radio-label"
>
{ t('eth') }
</label>
</div>
<div className="settings-tab__radio-button">
<input
type="radio"
id="fiat-primary-currency"
onChange={() => setUseETHAsPrimaryCurrencyPreference(false)}
checked={!useETHAsPrimaryCurrency}
/>
<label
htmlFor="fiat-primary-currency"
className="settings-tab__radio-label"
>
{ t('fiat') }
</label>
</div>
</div>
</div>
</div>
</div>
)
}
render () { render () {
const { warning, isMascara } = this.props const { warning, isMascara } = this.props
@ -346,6 +398,7 @@ export default class SettingsTab extends PureComponent {
<div className="settings-page__content"> <div className="settings-page__content">
{ warning && <div className="settings-tab__error">{ warning }</div> } { warning && <div className="settings-tab__error">{ warning }</div> }
{ this.renderCurrentConversion() } { this.renderCurrentConversion() }
{ this.renderUseEthAsPrimaryCurrency() }
{ this.renderCurrentLocale() } { this.renderCurrentLocale() }
{ this.renderNewRpcUrl() } { this.renderNewRpcUrl() }
{ this.renderStateLogs() } { this.renderStateLogs() }

View File

@ -11,7 +11,9 @@ import {
updateCurrentLocale, updateCurrentLocale,
setFeatureFlag, setFeatureFlag,
showModal, showModal,
setUseETHAsPrimaryCurrencyPreference,
} from '../../../../actions' } from '../../../../actions'
import { preferencesSelector } from '../../../../selectors'
const mapStateToProps = state => { const mapStateToProps = state => {
const { appState: { warning }, metamask } = state const { appState: { warning }, metamask } = state
@ -24,6 +26,7 @@ const mapStateToProps = state => {
isMascara, isMascara,
currentLocale, currentLocale,
} = metamask } = metamask
const { useETHAsPrimaryCurrency } = preferencesSelector(state)
return { return {
warning, warning,
@ -34,6 +37,7 @@ const mapStateToProps = state => {
useBlockie, useBlockie,
sendHexData, sendHexData,
provider, provider,
useETHAsPrimaryCurrency,
} }
} }
@ -50,6 +54,9 @@ const mapDispatchToProps = dispatch => {
}, },
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)), setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })), showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })),
setUseETHAsPrimaryCurrencyPreference: value => {
return dispatch(setUseETHAsPrimaryCurrencyPreference(value))
},
} }
} }

View File

@ -26,7 +26,7 @@ function QrCodeView () {
QrCodeView.prototype.render = function () { QrCodeView.prototype.render = function () {
const props = this.props const props = this.props
const { message, data } = props.Qr const { message, data } = props.Qr
const address = `${isHexPrefixed(data) ? 'ethereum:' : ''}${data}` const address = `${isHexPrefixed(data) ? 'ethereum:' : ''}${checksumAddress(data)}`
const qrImage = qrCode(4, 'M') const qrImage = qrCode(4, 'M')
qrImage.addData(address) qrImage.addData(address)
qrImage.make() qrImage.make()

View File

@ -2,7 +2,8 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { checksumAddress } from '../../../util' import { checksumAddress } from '../../../util'
import Identicon from '../../identicon' import Identicon from '../../identicon'
import CurrencyDisplay from '../currency-display' import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../../constants/common'
export default class AccountListItem extends Component { export default class AccountListItem extends Component {
@ -25,8 +26,6 @@ export default class AccountListItem extends Component {
const { const {
account, account,
className, className,
conversionRate,
currentCurrency,
displayAddress = false, displayAddress = false,
displayBalance = true, displayBalance = true,
handleClick, handleClick,
@ -57,16 +56,20 @@ export default class AccountListItem extends Component {
{ checksumAddress(address) } { checksumAddress(address) }
</div>} </div>}
{displayBalance && <CurrencyDisplay {
className="account-list-item__account-balances" displayBalance && (
conversionRate={conversionRate} <div className="account-list-item__account-balances">
convertedBalanceClassName="account-list-item__account-secondary-balance" <UserPreferencedCurrencyDisplay
convertedCurrency={currentCurrency} type={PRIMARY}
primaryBalanceClassName="account-list-item__account-primary-balance"
primaryCurrency="ETH"
readOnly={true}
value={balance} value={balance}
/>} />
<UserPreferencedCurrencyDisplay
type={SECONDARY}
value={balance}
/>
</div>
)
}
</div>) </div>)
} }

View File

@ -4,7 +4,7 @@ import { shallow } from 'enzyme'
import sinon from 'sinon' import sinon from 'sinon'
import proxyquire from 'proxyquire' import proxyquire from 'proxyquire'
import Identicon from '../../../identicon' import Identicon from '../../../identicon'
import CurrencyDisplay from '../../currency-display' import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display'
const utilsMethodStubs = { const utilsMethodStubs = {
checksumAddress: sinon.stub().returns('mockCheckSumAddress'), checksumAddress: sinon.stub().returns('mockCheckSumAddress'),
@ -114,17 +114,11 @@ describe('AccountListItem Component', function () {
it('should render a CurrencyDisplay with the correct props if displayBalance is true', () => { it('should render a CurrencyDisplay with the correct props if displayBalance is true', () => {
wrapper.setProps({ displayBalance: true }) wrapper.setProps({ displayBalance: true })
assert.equal(wrapper.find(CurrencyDisplay).length, 1) assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
assert.deepEqual( assert.deepEqual(
wrapper.find(CurrencyDisplay).props(), wrapper.find(UserPreferencedCurrencyDisplay).at(0).props(),
{ {
className: 'account-list-item__account-balances', type: 'PRIMARY',
conversionRate: 4,
convertedBalanceClassName: 'account-list-item__account-secondary-balance',
convertedCurrency: 'mockCurrentyCurrency',
primaryBalanceClassName: 'account-list-item__account-primary-balance',
primaryCurrency: 'ETH',
readOnly: true,
value: 'mockBalance', value: 'mockBalance',
} }
) )
@ -132,7 +126,7 @@ describe('AccountListItem Component', function () {
it('should not render a CurrencyDisplay if displayBalance is false', () => { it('should not render a CurrencyDisplay if displayBalance is false', () => {
wrapper.setProps({ displayBalance: false }) wrapper.setProps({ displayBalance: false })
assert.equal(wrapper.find(CurrencyDisplay).length, 0) assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 0)
}) })
}) })
}) })

View File

@ -1,186 +0,0 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const { conversionUtil, multiplyCurrencies } = require('../../../conversion-util')
const { removeLeadingZeroes } = require('../send.utils')
const currencyFormatter = require('currency-formatter')
const currencies = require('currency-formatter/currencies')
const ethUtil = require('ethereumjs-util')
const PropTypes = require('prop-types')
CurrencyDisplay.contextTypes = {
t: PropTypes.func,
}
module.exports = CurrencyDisplay
inherits(CurrencyDisplay, Component)
function CurrencyDisplay () {
Component.call(this)
}
function toHexWei (value) {
return conversionUtil(value, {
fromNumericBase: 'dec',
toNumericBase: 'hex',
toDenomination: 'WEI',
})
}
CurrencyDisplay.prototype.componentWillMount = function () {
this.setState({
valueToRender: this.getValueToRender(this.props),
})
}
CurrencyDisplay.prototype.componentWillReceiveProps = function (nextProps) {
const currentValueToRender = this.getValueToRender(this.props)
const newValueToRender = this.getValueToRender(nextProps)
if (currentValueToRender !== newValueToRender) {
this.setState({
valueToRender: newValueToRender,
})
}
}
CurrencyDisplay.prototype.getAmount = function (value) {
const { selectedToken } = this.props
const { decimals } = selectedToken || {}
const multiplier = Math.pow(10, Number(decimals || 0))
const sendAmount = multiplyCurrencies(value || '0', multiplier, {toNumericBase: 'hex'})
return selectedToken
? sendAmount
: toHexWei(value)
}
CurrencyDisplay.prototype.getValueToRender = function ({ selectedToken, conversionRate, value, readOnly }) {
if (value === '0x0') return readOnly ? '0' : ''
const { decimals, symbol } = selectedToken || {}
const multiplier = Math.pow(10, Number(decimals || 0))
return selectedToken
? conversionUtil(ethUtil.addHexPrefix(value), {
fromNumericBase: 'hex',
toNumericBase: 'dec',
toCurrency: symbol,
conversionRate: multiplier,
invertConversionRate: true,
})
: conversionUtil(ethUtil.addHexPrefix(value), {
fromNumericBase: 'hex',
toNumericBase: 'dec',
fromDenomination: 'WEI',
numberOfDecimals: 9,
conversionRate,
})
}
CurrencyDisplay.prototype.getConvertedValueToRender = function (nonFormattedValue) {
const { primaryCurrency, convertedCurrency, conversionRate } = this.props
if (conversionRate === 0 || conversionRate === null || conversionRate === undefined) {
if (nonFormattedValue !== 0) {
return null
}
}
let convertedValue = conversionUtil(nonFormattedValue, {
fromNumericBase: 'dec',
fromCurrency: primaryCurrency,
toCurrency: convertedCurrency,
numberOfDecimals: 2,
conversionRate,
})
convertedValue = Number(convertedValue).toFixed(2)
const upperCaseCurrencyCode = convertedCurrency.toUpperCase()
return currencies.find(currency => currency.code === upperCaseCurrencyCode)
? currencyFormatter.format(Number(convertedValue), {
code: upperCaseCurrencyCode,
})
: convertedValue
}
CurrencyDisplay.prototype.handleChange = function (newVal) {
this.setState({ valueToRender: removeLeadingZeroes(newVal) })
this.props.onChange(this.getAmount(newVal))
}
CurrencyDisplay.prototype.getInputWidth = function (valueToRender, readOnly) {
const valueString = String(valueToRender)
const valueLength = valueString.length || 1
const decimalPointDeficit = valueString.match(/\./) ? -0.5 : 0
return (valueLength + decimalPointDeficit + 0.75) + 'ch'
}
CurrencyDisplay.prototype.onlyRenderConversions = function (convertedValueToRender) {
const {
convertedBalanceClassName = 'currency-display__converted-value',
convertedCurrency,
} = this.props
return h('div', {
className: convertedBalanceClassName,
}, convertedValueToRender == null
? this.context.t('noConversionRateAvailable')
: `${convertedValueToRender} ${convertedCurrency.toUpperCase()}`
)
}
CurrencyDisplay.prototype.render = function () {
const {
className = 'currency-display',
primaryBalanceClassName = 'currency-display__input',
primaryCurrency,
readOnly = false,
inError = false,
onBlur,
step,
} = this.props
const { valueToRender } = this.state
const convertedValueToRender = this.getConvertedValueToRender(valueToRender)
return h('div', {
className,
style: {
borderColor: inError ? 'red' : null,
},
onClick: () => {
this.currencyInput && this.currencyInput.focus()
},
}, [
h('div.currency-display__primary-row', [
h('div.currency-display__input-wrapper', [
h('input', {
className: primaryBalanceClassName,
value: `${valueToRender}`,
placeholder: '0',
type: 'number',
readOnly,
...(!readOnly ? {
onChange: e => this.handleChange(e.target.value),
onBlur: () => onBlur(this.getAmount(valueToRender)),
} : {}),
ref: input => { this.currencyInput = input },
style: {
width: this.getInputWidth(valueToRender, readOnly),
},
min: 0,
step,
}),
h('span.currency-display__currency-symbol', primaryCurrency),
]),
]), this.onlyRenderConversions(convertedValueToRender),
])
}

View File

@ -1 +0,0 @@
export { default } from './currency-display.js'

View File

@ -1,91 +0,0 @@
import React from 'react'
import assert from 'assert'
import sinon from 'sinon'
import { shallow, mount } from 'enzyme'
import CurrencyDisplay from '../currency-display'
describe('', () => {
const token = {
address: '0xTest',
symbol: 'TST',
decimals: '13',
}
it('retuns ETH value for wei value', () => {
const wrapper = mount(<CurrencyDisplay />, {context: {t: str => str + '_t'}})
const value = wrapper.instance().getValueToRender({
// 1000000000000000000
value: 'DE0B6B3A7640000',
})
assert.equal(value, 1)
})
it('returns value of token based on token decimals', () => {
const wrapper = mount(<CurrencyDisplay />, {context: {t: str => str + '_t'}})
const value = wrapper.instance().getValueToRender({
selectedToken: token,
// 1000000000000000000
value: 'DE0B6B3A7640000',
})
assert.equal(value, 100000)
})
it('returns hex value with decimal adjustment', () => {
const wrapper = mount(
<CurrencyDisplay
selectedToken={token}
/>, {context: {t: str => str + '_t'}})
const value = wrapper.instance().getAmount(1)
// 10000000000000
assert.equal(value, '9184e72a000')
})
it('#getConvertedValueToRender converts input value based on conversionRate', () => {
const wrapper = mount(
<CurrencyDisplay
primaryCurrency={'usd'}
convertedCurrency={'ja'}
conversionRate={2}
/>, {context: {t: str => str + '_t'}})
const value = wrapper.instance().getConvertedValueToRender(32)
assert.equal(value, 64)
})
it('#onlyRenderConversions renders single element for converted currency and value', () => {
const wrapper = mount(
<CurrencyDisplay
convertedCurrency={'test'}
/>, {context: {t: str => str + '_t'}})
const value = wrapper.instance().onlyRenderConversions(10)
assert.equal(value.props.className, 'currency-display__converted-value')
assert.equal(value.props.children, '10 TEST')
})
it('simulates change value in input', () => {
const handleChangeSpy = sinon.spy()
const wrapper = shallow(
<CurrencyDisplay
onChange={handleChangeSpy}
/>, {context: {t: str => str + '_t'}})
const input = wrapper.find('input')
input.simulate('focus')
input.simulate('change', { target: { value: '100' } })
assert.equal(wrapper.state().valueToRender, '100')
assert.equal(wrapper.find('input').prop('value'), '100')
})
})

View File

@ -34,19 +34,25 @@ export default class AmountMaxButton extends Component {
}) })
} }
render () { onMaxClick = (event) => {
const { setMaxModeTo, maxModeOn } = this.props const { setMaxModeTo } = this.props
return (
<div
className="send-v2__amount-max"
onClick={(event) => {
event.preventDefault() event.preventDefault()
setMaxModeTo(true) setMaxModeTo(true)
this.setMaxAmount() this.setMaxAmount()
}} }
render () {
return this.props.maxModeOn
? null
: (
<div>
<span
className="send-v2__amount-max"
onClick={this.onMaxClick}
> >
{!maxModeOn ? this.context.t('max') : ''} {this.context.t('max')}
</span>
</div> </div>
) )
} }

View File

@ -56,9 +56,8 @@ describe('AmountMaxButton Component', function () {
}) })
describe('render', () => { describe('render', () => {
it('should render a div with a send-v2__amount-max class', () => { it('should render an element with a send-v2__amount-max class', () => {
assert.equal(wrapper.find('.send-v2__amount-max').length, 1) assert(wrapper.exists('.send-v2__amount-max'))
assert(wrapper.find('.send-v2__amount-max').is('div'))
}) })
it('should call setMaxModeTo and setMaxAmount when the send-v2__amount-max div is clicked', () => { it('should call setMaxModeTo and setMaxAmount when the send-v2__amount-max div is clicked', () => {
@ -77,9 +76,9 @@ describe('AmountMaxButton Component', function () {
) )
}) })
it('should not render text when maxModeOn is true', () => { it('should not render anything when maxModeOn is true', () => {
wrapper.setProps({ maxModeOn: true }) wrapper.setProps({ maxModeOn: true })
assert.equal(wrapper.find('.send-v2__amount-max').text(), '') assert.ok(!wrapper.exists('.send-v2__amount-max'))
}) })
it('should render the expected text when maxModeOn is false', () => { it('should render the expected text when maxModeOn is false', () => {

View File

@ -2,7 +2,8 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import SendRowWrapper from '../send-row-wrapper/' import SendRowWrapper from '../send-row-wrapper/'
import AmountMaxButton from './amount-max-button/' import AmountMaxButton from './amount-max-button/'
import CurrencyDisplay from '../../currency-display' import UserPreferencedCurrencyInput from '../../../user-preferenced-currency-input'
import UserPreferencedTokenInput from '../../../user-preferenced-token-input'
export default class SendAmountRow extends Component { export default class SendAmountRow extends Component {
@ -84,16 +85,25 @@ export default class SendAmountRow extends Component {
} }
} }
renderInput () {
const { amount, inError, selectedToken } = this.props
const Component = selectedToken ? UserPreferencedTokenInput : UserPreferencedCurrencyInput
return (
<Component
onChange={newAmount => this.validateAmount(newAmount)}
onBlur={newAmount => {
this.updateGas(newAmount)
this.updateAmount(newAmount)
}}
error={inError}
value={amount}
/>
)
}
render () { render () {
const { const { gasTotal, inError } = this.props
amount,
amountConversionRate,
convertedCurrency,
gasTotal,
inError,
primaryCurrency,
selectedToken,
} = this.props
return ( return (
<SendRowWrapper <SendRowWrapper
@ -102,20 +112,7 @@ export default class SendAmountRow extends Component {
errorType={'amount'} errorType={'amount'}
> >
{!inError && gasTotal && <AmountMaxButton />} {!inError && gasTotal && <AmountMaxButton />}
<CurrencyDisplay { this.renderInput() }
conversionRate={amountConversionRate}
convertedCurrency={convertedCurrency}
onBlur={newAmount => {
this.updateGas(newAmount)
this.updateAmount(newAmount)
}}
onChange={newAmount => this.validateAmount(newAmount)}
inError={inError}
primaryCurrency={primaryCurrency || 'ETH'}
selectedToken={selectedToken}
value={amount}
step="any"
/>
</SendRowWrapper> </SendRowWrapper>
) )
} }

View File

@ -6,7 +6,7 @@ import SendAmountRow from '../send-amount-row.component.js'
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component' import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
import AmountMaxButton from '../amount-max-button/amount-max-button.container' import AmountMaxButton from '../amount-max-button/amount-max-button.container'
import CurrencyDisplay from '../../../currency-display' import UserPreferencedTokenInput from '../../../../user-preferenced-token-input'
const propsMethodSpies = { const propsMethodSpies = {
setMaxModeTo: sinon.spy(), setMaxModeTo: sinon.spy(),
@ -150,26 +150,19 @@ describe('SendAmountRow Component', function () {
assert(wrapper.find(SendRowWrapper).childAt(0).is(AmountMaxButton)) assert(wrapper.find(SendRowWrapper).childAt(0).is(AmountMaxButton))
}) })
it('should render a CurrencyDisplay as the second child of the SendRowWrapper', () => { it('should render a UserPreferencedTokenInput as the second child of the SendRowWrapper', () => {
assert(wrapper.find(SendRowWrapper).childAt(1).is(CurrencyDisplay)) console.log('HI', wrapper.find(SendRowWrapper).childAt(1))
assert(wrapper.find(SendRowWrapper).childAt(1).is(UserPreferencedTokenInput))
}) })
it('should render the CurrencyDisplay with the correct props', () => { it('should render the UserPreferencedTokenInput with the correct props', () => {
const { const {
conversionRate,
convertedCurrency,
onBlur, onBlur,
onChange, onChange,
inError, error,
primaryCurrency,
selectedToken,
value, value,
} = wrapper.find(SendRowWrapper).childAt(1).props() } = wrapper.find(SendRowWrapper).childAt(1).props()
assert.equal(conversionRate, 'mockAmountConversionRate') assert.equal(error, false)
assert.equal(convertedCurrency, 'mockConvertedCurrency')
assert.equal(inError, false)
assert.equal(primaryCurrency, 'mockPrimaryCurrency')
assert.deepEqual(selectedToken, { address: 'mockTokenAddress' })
assert.equal(value, 'mockAmount') assert.equal(value, 'mockAmount')
assert.equal(SendAmountRow.prototype.updateGas.callCount, 0) assert.equal(SendAmountRow.prototype.updateGas.callCount, 0)
assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0) assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0)
@ -192,11 +185,5 @@ describe('SendAmountRow Component', function () {
['mockNewAmount'] ['mockNewAmount']
) )
}) })
it('should pass the default primaryCurrency to the CurrencyDisplay if primaryCurrency is falsy', () => {
wrapper.setProps({ primaryCurrency: null })
const { primaryCurrency } = wrapper.find(SendRowWrapper).childAt(1).props()
assert.equal(primaryCurrency, 'ETH')
})
}) })
}) })

View File

@ -1,7 +1,7 @@
import React, {Component} from 'react' import React, {Component} from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import CurrencyDisplay from '../../../../send/currency-display' import UserPreferencedCurrencyDisplay from '../../../../user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../../../../constants/common'
export default class GasFeeDisplay extends Component { export default class GasFeeDisplay extends Component {
@ -19,27 +19,24 @@ export default class GasFeeDisplay extends Component {
}; };
render () { render () {
const { const { gasTotal, onClick, gasLoadingError } = this.props
conversionRate,
gasTotal,
onClick,
primaryCurrency = 'ETH',
convertedCurrency,
gasLoadingError,
} = this.props
return ( return (
<div className="send-v2__gas-fee-display"> <div className="send-v2__gas-fee-display">
{gasTotal {gasTotal
? <CurrencyDisplay ? (
primaryCurrency={primaryCurrency} <div className="currency-display">
convertedCurrency={convertedCurrency} <UserPreferencedCurrencyDisplay
value={gasTotal} value={gasTotal}
conversionRate={conversionRate} type={PRIMARY}
gasLoadingError={gasLoadingError}
convertedPrefix={'$'}
readOnly
/> />
<UserPreferencedCurrencyDisplay
className="currency-display__converted-value"
value={gasTotal}
type={SECONDARY}
/>
</div>
)
: gasLoadingError : gasLoadingError
? <div className="currency-display.currency-display--message"> ? <div className="currency-display.currency-display--message">
{this.context.t('setGasPrice')} {this.context.t('setGasPrice')}

View File

@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert' import assert from 'assert'
import {shallow} from 'enzyme' import {shallow} from 'enzyme'
import GasFeeDisplay from '../gas-fee-display.component' import GasFeeDisplay from '../gas-fee-display.component'
import CurrencyDisplay from '../../../../../send/currency-display' import UserPreferencedCurrencyDisplay from '../../../../../user-preferenced-currency-display'
import sinon from 'sinon' import sinon from 'sinon'
@ -29,17 +29,15 @@ describe('SendGasRow Component', function () {
describe('render', () => { describe('render', () => {
it('should render a CurrencyDisplay component', () => { it('should render a CurrencyDisplay component', () => {
assert.equal(wrapper.find(CurrencyDisplay).length, 1) assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
}) })
it('should render the CurrencyDisplay with the correct props', () => { it('should render the CurrencyDisplay with the correct props', () => {
const { const {
conversionRate, type,
convertedCurrency,
value, value,
} = wrapper.find(CurrencyDisplay).props() } = wrapper.find(UserPreferencedCurrencyDisplay).at(0).props()
assert.equal(conversionRate, 20) assert.equal(type, 'PRIMARY')
assert.equal(convertedCurrency, 'mockConvertedCurrency')
assert.equal(value, 'mockGasTotal') assert.equal(value, 'mockGasTotal')
}) })

View File

@ -5,6 +5,7 @@ const inherits = require('util').inherits
const AccountListItem = require('../account-list-item/account-list-item.component').default const AccountListItem = require('../account-list-item/account-list-item.component').default
const connect = require('react-redux').connect const connect = require('react-redux').connect
const Tooltip = require('../../tooltip') const Tooltip = require('../../tooltip')
const checksumAddress = require('../../../util').checksumAddress
ToAutoComplete.contextTypes = { ToAutoComplete.contextTypes = {
t: PropTypes.func, t: PropTypes.func,
@ -48,7 +49,7 @@ ToAutoComplete.prototype.renderDropdown = function () {
account, account,
className: 'account-list-item__dropdown', className: 'account-list-item__dropdown',
handleClick: () => { handleClick: () => {
onChange(account.address) onChange(checksumAddress(account.address))
closeDropdown() closeDropdown()
}, },
icon: this.getListItemIcon(account.address, to), icon: this.getListItemIcon(account.address, to),

View File

@ -5,6 +5,7 @@ import Identicon from '../identicon'
import Tooltip from '../tooltip-v2' import Tooltip from '../tooltip-v2'
import copyToClipboard from 'copy-to-clipboard' import copyToClipboard from 'copy-to-clipboard'
import { DEFAULT_VARIANT, CARDS_VARIANT } from './sender-to-recipient.constants' import { DEFAULT_VARIANT, CARDS_VARIANT } from './sender-to-recipient.constants'
import { checksumAddress } from '../../util'
const variantHash = { const variantHash = {
[DEFAULT_VARIANT]: 'sender-to-recipient--default', [DEFAULT_VARIANT]: 'sender-to-recipient--default',
@ -40,7 +41,7 @@ export default class SenderToRecipient extends PureComponent {
return !this.props.addressOnly && ( return !this.props.addressOnly && (
<div className="sender-to-recipient__sender-icon"> <div className="sender-to-recipient__sender-icon">
<Identicon <Identicon
address={this.props.senderAddress} address={checksumAddress(this.props.senderAddress)}
diameter={24} diameter={24}
/> />
</div> </div>
@ -50,6 +51,7 @@ export default class SenderToRecipient extends PureComponent {
renderSenderAddress () { renderSenderAddress () {
const { t } = this.context const { t } = this.context
const { senderName, senderAddress, addressOnly } = this.props const { senderName, senderAddress, addressOnly } = this.props
const checksummedSenderAddress = checksumAddress(senderAddress)
return ( return (
<Tooltip <Tooltip
@ -60,7 +62,7 @@ export default class SenderToRecipient extends PureComponent {
onHidden={() => this.setState({ senderAddressCopied: false })} onHidden={() => this.setState({ senderAddressCopied: false })}
> >
<div className="sender-to-recipient__name"> <div className="sender-to-recipient__name">
{ addressOnly ? `${t('from')}: ${senderAddress}` : senderName } { addressOnly ? `${t('from')}: ${checksummedSenderAddress}` : senderName }
</div> </div>
</Tooltip> </Tooltip>
) )
@ -68,11 +70,12 @@ export default class SenderToRecipient extends PureComponent {
renderRecipientIdenticon () { renderRecipientIdenticon () {
const { recipientAddress, assetImage } = this.props const { recipientAddress, assetImage } = this.props
const checksummedRecipientAddress = checksumAddress(recipientAddress)
return !this.props.addressOnly && ( return !this.props.addressOnly && (
<div className="sender-to-recipient__sender-icon"> <div className="sender-to-recipient__sender-icon">
<Identicon <Identicon
address={recipientAddress} address={checksummedRecipientAddress}
diameter={24} diameter={24}
image={assetImage} image={assetImage}
/> />
@ -83,13 +86,14 @@ export default class SenderToRecipient extends PureComponent {
renderRecipientWithAddress () { renderRecipientWithAddress () {
const { t } = this.context const { t } = this.context
const { recipientName, recipientAddress, addressOnly } = this.props const { recipientName, recipientAddress, addressOnly } = this.props
const checksummedRecipientAddress = checksumAddress(recipientAddress)
return ( return (
<div <div
className="sender-to-recipient__party sender-to-recipient__party--recipient sender-to-recipient__party--recipient-with-address" className="sender-to-recipient__party sender-to-recipient__party--recipient sender-to-recipient__party--recipient-with-address"
onClick={() => { onClick={() => {
this.setState({ recipientAddressCopied: true }) this.setState({ recipientAddressCopied: true })
copyToClipboard(recipientAddress) copyToClipboard(checksummedRecipientAddress)
}} }}
> >
{ this.renderRecipientIdenticon() } { this.renderRecipientIdenticon() }
@ -103,7 +107,7 @@ export default class SenderToRecipient extends PureComponent {
<div className="sender-to-recipient__name"> <div className="sender-to-recipient__name">
{ {
addressOnly addressOnly
? `${t('to')}: ${recipientAddress}` ? `${t('to')}: ${checksummedRecipientAddress}`
: (recipientName || this.context.t('newContract')) : (recipientName || this.context.t('newContract'))
} }
</div> </div>
@ -147,6 +151,7 @@ export default class SenderToRecipient extends PureComponent {
render () { render () {
const { senderAddress, recipientAddress, variant } = this.props const { senderAddress, recipientAddress, variant } = this.props
const checksummedSenderAddress = checksumAddress(senderAddress)
return ( return (
<div className={classnames(variantHash[variant])}> <div className={classnames(variantHash[variant])}>
@ -154,7 +159,7 @@ export default class SenderToRecipient extends PureComponent {
className={classnames('sender-to-recipient__party sender-to-recipient__party--sender')} className={classnames('sender-to-recipient__party sender-to-recipient__party--sender')}
onClick={() => { onClick={() => {
this.setState({ senderAddressCopied: true }) this.setState({ senderAddressCopied: true })
copyToClipboard(senderAddress) copyToClipboard(checksummedSenderAddress)
}} }}
> >
{ this.renderSenderIdenticon() } { this.renderSenderIdenticon() }

View File

@ -52,12 +52,12 @@ ShiftListItem.prototype.render = function () {
}, },
}, [ }, [
h('img', { h('img', {
src: 'https://info.shapeshift.io/sites/default/files/logo.png', src: 'https://shapeshift.io/logo.png',
style: { style: {
height: '35px', height: '35px',
width: '132px', width: '132px',
position: 'absolute', position: 'absolute',
clip: 'rect(0px,23px,34px,0px)', clip: 'rect(0px,30px,34px,0px)',
}, },
}), }),
]), ]),
@ -132,7 +132,6 @@ ShiftListItem.prototype.renderInfo = function () {
case 'no_deposits': case 'no_deposits':
return h('.flex-column', { return h('.flex-column', {
style: { style: {
width: '200px',
overflow: 'hidden', overflow: 'hidden',
}, },
}, [ }, [

View File

@ -204,7 +204,7 @@ SignatureRequest.prototype.renderBody = function () {
h('span.request-signature__help-link', { h('span.request-signature__help-link', {
onClick: () => { onClick: () => {
global.platform.openWindow({ global.platform.openWindow({
url: 'https://consensys.zendesk.com/hc/en-us/articles/360004427792', url: 'https://metamask.zendesk.com/hc/en-us/articles/360015488751',
}) })
}, },
}, this.context.t('learnMore'))] }, this.context.t('learnMore'))]

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