diff options
128 files changed, 8548 insertions, 3405 deletions
@@ -148,7 +148,7 @@ "space-in-parens": [1, "never"], "space-infix-ops": 2, "space-unary-ops": [2, { "words": true, "nonwords": false }], - "spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], + "spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","], "exceptions": ["=", "-"] } ], "strict": 0, "template-curly-spacing": [2, "never"], "use-isnan": 2, diff --git a/.gitignore b/.gitignore index 2bf38cad4..f2a678777 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,9 @@ app/bower_components test/bower_components package +# IDEs .idea +.vscode temp .tmp diff --git a/CHANGELOG.md b/CHANGELOG.md index e7d4a09fe..40d77bc9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,15 @@ ## Current Master -- Add ability for internationalization. +## 4.3.0 Wed Mar 21 2018 + +- (beta) Add internationalization support! Includes translations for 13 (!!) new languages: French, Spanish, Italian, German, Dutch, Portuguese, Japanese, Korean, Vietnamese, Mandarin, Hindi, Tagalog, and Russian! Select "Try Beta" in the menu to take them for a spin. Read more about the community effort [here](https://medium.com/gitcoin/metamask-internationalizes-via-gitcoin-bf1390c0301c) +- No longer uses nonces specified by the dapp - Will now throw an error if the `to` field in txParams is not valid. - Will strip null values from the `to` field. -- Fix flashing to Log in screen after logging in or restoring from seed phrase. +- (beta) No longer shows token confirmation screen when performing a non-send +- (beta) Fixes bug where tx data was nullified when repricing a tx +- Fix flashing Login screen after logging in or restoring from seed phrase. - Increase tap areas for menu buttons on mobile - Change all fonts in new-ui onboarding to Roboto, size 400 - Add a welcome screen to new-ui onboarding flow @@ -1,5 +1,5 @@ # 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)](http://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) [![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) ## Support @@ -76,5 +76,3 @@ To write tests that will be run in the browser using QUnit, add your test files - [How to generate a visualization of this repository's development](./docs/development-visualization.md) [1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BaccountManager%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A - - diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json new file mode 100644 index 000000000..0bdce516c --- /dev/null +++ b/app/_locales/de/messages.json @@ -0,0 +1,873 @@ +{ + "accept": { + "message": "Annehmen" + }, + "account": { + "message": "Account" + }, + "accountDetails": { + "message": "Accountdetails" + }, + "accountName": { + "message": "Accountname" + }, + "address": { + "message": "Adresse" + }, + "addToken": { + "message": "Token hinzufügen" + }, + "amount": { + "message": "Betrag" + }, + "amountPlusGas": { + "message": "Betrag + Gas" + }, + "appDescription": { + "message": "Ethereum Browsererweiterung", + "description": "Die Beschreibung der Erweiterung" + }, + "appName": { + "message": "MetaMask", + "description": "Der Name der Erweiterung" + }, + "attemptingConnect": { + "message": "Versuch mit der Blockchain zu verbinden." + }, + "available": { + "message": "Verfügbar" + }, + "back": { + "message": "Zurück" + }, + "balance": { + "message": "Guthaben:" + }, + "balanceIsInsufficientGas": { + "message": "Guthaben unzureichend für den aktuellen gesamten Gasbetrag" + }, + "beta": { + "message": "BETA" + }, + "betweenMinAndMax": { + "message": "Muss größer oder gleich $1 und kleiner oder gleich $2 sein.", + "description": "Helfer für die Eingabe von hex als dezimal" + }, + "blockiesIdenticon": { + "message": "Blockies Identicon verwenden" + }, + "borrowDharma": { + "message": "Mit Dharma ausleihen (Beta)" + }, + "builtInCalifornia": { + "message": "MetaMask wurde in Kalifornien entwickelt und gebaut." + }, + "buy": { + "message": "Kaufen" + }, + "buyCoinbase": { + "message": "Auf Coinbase kaufen" + }, + "buyCoinbaseExplainer": { + "message": "Coinbase ist die weltweit bekannteste Möglichkeit bitcoin, ethereum und litecoin zu kaufen und verkaufen." + }, + "ok": { + "message": "Ok" + }, + "cancel": { + "message": "Abbrechen" + }, + "classicInterface": { + "message": "Klassische Oberfläche verwenden" + }, + "clickCopy": { + "message": "Klicken um zu kopieren" + }, + "confirm": { + "message": "Bestätigen" + }, + "confirmed": { + "message": "Bestätigt" + }, + "confirmContract": { + "message": "Smart Contract bestätigen" + }, + "confirmPassword": { + "message": "Passwort bestätigen" + }, + "confirmTransaction": { + "message": "Transaktion bestätigen" + }, + "continue": { + "message": "Weiter" + }, + "continueToCoinbase": { + "message": "Zu Coinbase fortfahren" + }, + "contractDeployment": { + "message": "Smart Contract ausführen" + }, + "conversionProgress": { + "message": "Umtausch in Arbeit" + }, + "copiedButton": { + "message": "Kopiert" + }, + "copiedClipboard": { + "message": "In die Zwischenablage kopiert" + }, + "copiedExclamation": { + "message": "Kopiert!" + }, + "copiedSafe": { + "message": "Ich habe es an einen sicheren Ort kopiert" + }, + "copy": { + "message": "Kopieren" + }, + "copyToClipboard": { + "message": "In die Zwischenablage kopieren" + }, + "copyButton": { + "message": " Kopieren " + }, + "copyPrivateKey": { + "message": "Das ist Ihr Private Key (klicken um zu kopieren)" + }, + "create": { + "message": "Erstellen" + }, + "createAccount": { + "message": "Account erstellen" + }, + "createDen": { + "message": "Erstellen" + }, + "crypto": { + "message": "Krypto", + "description": "Börsentyp (Kryptowährungen)" + }, + "currentConversion": { + "message": "Aktueller Umtausch" + }, + "currentNetwork": { + "message": "Aktuelles Netzwerk" + }, + "customGas": { + "message": "Gas anpassen" + }, + "customize": { + "message": "Anpassen" + }, + "customRPC": { + "message": "Spezieller RPC" + }, + "decimalsMustZerotoTen": { + "message": "Die Dezimalangabe muss mindestens 0 und nicht höher als 36 sein." + }, + "decimal": { + "message": "Dezimalangabe der Präzision" + }, + "defaultNetwork": { + "message": "Das Standardnetzwerk für Ether Transaktionen ist das Main Net." + }, + "denExplainer": { + "message": "Dein DEN ist dein passwortverschlüsselter Speicher innerhalb von MetaMask." + }, + "deposit": { + "message": "Einzahlen" + }, + "depositBTC": { + "message": "Zahle dein BTC in die unten stehende Adresse ein:" + }, + "depositCoin": { + "message": "Zahle deine $1 in die unten stehende Adresse ein", + "description": "Teilt dem Benutzer mit welchen Token er beim Einzahlen mit Shapeshift ausgewählt hat" + }, + "depositEth": { + "message": "Eth einzahlen" + }, + "depositEther": { + "message": "Ether einzahlen" + }, + "depositFiat": { + "message": "Fiat einzahlen" + }, + "depositFromAccount": { + "message": "Von einem anderen Account einzahlen" + }, + "depositShapeShift": { + "message": "Mit ShapeShift einzahlen" + }, + "depositShapeShiftExplainer": { + "message": "Wenn du andere Kryptowährungen besitzt, kannst du diese direkt mit Hilfe deiner MetaMask Wallet handeln und einzahlen. Du benötigst keinen Account." + }, + "details": { + "message": "Details" + }, + "directDeposit": { + "message": "Sofortige Einzahlung" + }, + "directDepositEther": { + "message": "Sofort Ether einzahlen" + }, + "directDepositEtherExplainer": { + "message": "Wenn du bereits Ether besitzt, ist die sofortige Einzahlung die schnellste Methode Ether in deine neue Wallet zu bekommen." + }, + "done": { + "message": "Fertig" + }, + "downloadStatelogs": { + "message": "Statelogs herunterladen" + }, + "dropped": { + "message": "Abgewählt" + }, + "edit": { + "message": "Editieren" + }, + "editAccountName": { + "message": "Namen des Accounts editieren" + }, + "emailUs": { + "message": "Schreib uns eine Mail!" + }, + "encryptNewDen": { + "message": "Verschlüssele deine neue DEN" + }, + "enterPassword": { + "message": "Passwort eingeben" + }, + "enterPasswordConfirm": { + "message": "Gib dein neues Passwort zur Bestätigung ein" + }, + "passwordNotLongEnough": { + "message": "Passwort ist nicht lang genug" + }, + "passwordsDontMatch": { + "message": "Passwörter stimmen nicht überein" + }, + "etherscanView": { + "message": "Account auf Etherscan anschauen" + }, + "exchangeRate": { + "message": "Wechselrate" + }, + "exportPrivateKey": { + "message": "Private Key exportieren" + }, + "exportPrivateKeyWarning": { + "message": "Der Export von Private Keys verläuft auf eigene Verantwortung." + }, + "failed": { + "message": "Fehlgeschlagen" + }, + "fiat": { + "message": "FIAT", + "description": "Börsentyp" + }, + "fileImportFail": { + "message": "Dateiimport fehlgeschlagen? Bitte hier klicken!", + "description": "Hilft dem Benutzer sein Benutzerkonto durch eine JSON Datei zu importieren" + }, + "followTwitter": { + "message": "Folge uns auf Twitter" + }, + "from": { + "message": "von" + }, + "fromToSame": { + "message": "Ziel- und Ursprungsadresse dürfen nicht identisch sein" + }, + "fromShapeShift": { + "message": "Von ShapeShift" + }, + "gas": { + "message": "Gas", + "description": "Kleiner Hinweis bzgl. der Gaskosten" + }, + "gasFee": { + "message": "Gasgebühren" + }, + "gasLimit": { + "message": "Gaslimit" + }, + "gasLimitCalculation": { + "message": "Wir berechnen das empfohlene Gaslimit basierend auf der Erfolgsrate des Netzwerks." + }, + "gasLimitRequired": { + "message": "Gaslimit benötigt" + }, + "gasLimitTooLow": { + "message": "Gaslimit muss mindestens 21000 betragen" + }, + "generatingSeed": { + "message": "Seed generieren..." + }, + "gasPrice": { + "message": "Gaspreis (GWEI)" + }, + "gasPriceCalculation": { + "message": "Wir berechnen den empfohlenen Gaspreis basierend auf der Erfolgsrate des Netzwerks." + }, + "gasPriceRequired": { + "message": "Gaspreis benötigt" + }, + "getEther": { + "message": "Ether holen" + }, + "getEtherFromFaucet": { + "message": "Ether für $1 vom Faucet holen", + "description": "Zeigt den Netzwerknamen für den Ether Faucet an" + }, + "greaterThanMin": { + "message": "Muss größer oder gleich $1 sein.", + "description": "Helfer für die Eingabe von hex als dezimal" + }, + "here": { + "message": "hier", + "description": "z.B. für klick hier für mehr Informationen (in Zusammenhang mit troubleTokenBalances)" + }, + "hereList": { + "message": "Hier ist eine Liste!!!!" + }, + "hide": { + "message": "Ausblenden" + }, + "hideToken": { + "message": "Token ausblenden" + }, + "hideTokenPrompt": { + "message": "Token ausblenden?" + }, + "howToDeposit": { + "message": "Wie möchtest du Ether einzahlen?" + }, + "holdEther": { + "message": "Es erlaubt dir ether & Token zu halten und dient dir als Verbindung zu dezentralisierten Applikationen." + }, + "import": { + "message": "Import", + "description": "Button um den Account aus einer ausgewählten Datei zu importieren" + }, + "importAccount": { + "message": "Account importieren" + }, + "importAccountMsg": { + "message":" Importierte Accounts werden nicht mit der Seed Wörterfolge deines ursprünglichen MetaMask Accounts verknüpft. Erfahre mehr über importierte Accounts." + }, + "importAnAccount": { + "message": "Einen Account importieren" + }, + "importDen": { + "message": "Vorhandenes DEN importieren" + }, + "imported": { + "message": "Importiert", + "description": "Status der angezeigt wird wenn ein Benutzerkonto vollständig in das Schlüsselbund geladen wurde." + }, + "infoHelp": { + "message": "Info & Hilfe" + }, + "insufficientFunds": { + "message": "Nicht genügend Guthaben." + }, + "insufficientTokens": { + "message": "Nicht genügend Token." + }, + "invalidAddress": { + "message": "Ungültige Adresse" + }, + "invalidAddressRecipient": { + "message": "Empfängeradresse ist unzulässig" + }, + "invalidGasParams": { + "message": "Ungültige Gasparameter" + }, + "invalidInput": { + "message": "Ungültige Eingabe." + }, + "invalidRequest": { + "message": "Ungültige Abfrage" + }, + "invalidRPC": { + "message": "Ungültige RPC URI" + }, + "jsonFail": { + "message": "Irgendetwas ist schief gelaufen. Bitte überprüfe ob deine JSON Datei korrekt formatiert ist." + }, + "jsonFile": { + "message": "JSON Datei", + "description": "Dateiformat für das Importieren eines Accounts" + }, + "kovan": { + "message": "Kovan Testnetzwerk" + }, + "knowledgeDataBase": { + "message": "Schau in unsere Wissensdatenbank" + }, + "max": { + "message": "Max" + }, + "lessThanMax": { + "message": "Muss kleiner oder gleich $1 sein.", + "description": "Helfer für die Eingabe von hex als dezimal" + }, + "likeToAddTokens": { + "message": "Möchtest du diese Token hinzufügen?" + }, + "links": { + "message": "Links" + }, + "limit": { + "message": "Limit" + }, + "loading": { + "message": "Laden..." + }, + "loadingTokens": { + "message": "Token laden..." + }, + "localhost": { + "message": "Localhost 8545" + }, + "login": { + "message": "Login" + }, + "logout": { + "message": "Ausloggen" + }, + "loose": { + "message": "Frei" + }, + "loweCaseWords": { + "message": "Die Wörter der Seed Wörterfolgen sind alle kleingeschrieben" + }, + "mainnet": { + "message": "Ethereum Hauptnetzwerk (Main Net)" + }, + "message": { + "message": "Nachricht" + }, + "metamaskDescription": { + "message": "MetaMask ist ein sicherer Identitätssafe für Ethereum." + }, + "min": { + "message": "Minimum" + }, + "myAccounts": { + "message": "Meine Accounts" + }, + "mustSelectOne": { + "message": "Du musst mindestens 1 Token auswählen." + }, + "needEtherInWallet": { + "message": "Um dezentralisierte Applikationen mit MetaMask verwenden zu können, benötigst du Ether in deiner Wallet." + }, + "needImportFile": { + "message": "Für den Import musst du eine Datei auswählen.", + "description": "Benutzer importiert ein Benutzerkonto und muss eine Datei hinzufügen um fortzufahren" + }, + "needImportPassword": { + "message": "Für die ausgewählte Datei muss ein Passwort eingegeben werden.", + "description": "Passwort und Datei sind notwendig um einen Account zu importieren" + }, + "negativeETH": { + "message": "Negative ETH Beträge können nicht versendet werden." + }, + "networks": { + "message": "Netzwerke" + }, + "newAccount": { + "message": "Neuer Account" + }, + "newAccountNumberName": { + "message": "Account $1", + "description": "Standardname für einen weiteren Account der angelegt wird, wenn create account screen geklickt wird" + }, + "newContract": { + "message": "Neuer Smart Contract" + }, + "newPassword": { + "message": "Neues Passwort (min. 8 Zeichen)" + }, + "newRecipient": { + "message": "Neuer Empfänger" + }, + "newRPC": { + "message": "Neue RPC URL" + }, + "next": { + "message": "Weiter" + }, + "noAddressForName": { + "message": "Für den angegebene Namen wurde keine Adresse eingegeben." + }, + "noDeposits": { + "message": "Keine Einzahlung erhalten" + }, + "noTransactionHistory": { + "message": "Keine Transaktionshistorie." + }, + "noTransactions": { + "message": "Keine Transaktionen" + }, + "notStarted": { + "message": "Nicht gestartet" + }, + "oldUI": { + "message": "Alte Oberfläche" + }, + "oldUIMessage": { + "message": "Du bist zur alten Oberfläche zurückgewechselt. Du kannst mit Hilfe der Option im oberen rechten Dropdown Menü zur neuen Oberfläche zurückwechseln." + }, + "or": { + "message": "oder", + "description": "Wahl zwischen erstellen oder importieren eines Accounts." + }, + "passwordCorrect": { + "message": "Bitte überzeuge dich davon, dass dein Passwort korrekt ist." + }, + "passwordMismatch": { + "message": "Passwörter stimmen nicht überein", + "description": "Im Passwort erstellen Prozess stimmen beide Passwörter nicht miteinander überein" + }, + "passwordShort": { + "message": "Passwort ist nicht lang genug", + "description": "Im Passwort erstellen Prozess ist das eingegebene Passwort nicht lang genug um sicher zu sein" + }, + "pastePrivateKey": { + "message": "Füge deine Private Key Zeichenfolge hier ein:", + "description": "Für den Import eine Accounts mit Hilfe eines Private Keys" + }, + "pasteSeed": { + "message": "Füge deine Seed Wörterfolge hier ein!" + }, + "personalAddressDetected": { + "message": "Personalisierte Adresse identifiziert. Bitte füge die Token Contract Adresse ein." + }, + "pleaseReviewTransaction": { + "message": "Bitte überprüfe deine Transaktion." + }, + "privacyMsg": { + "message": "Datenschutzrichtlinie" + }, + "privateKey": { + "message": "Private Key", + "description": "Wähle diesen Dateityp um damit einen Account zu importieren" + }, + "privateKeyWarning": { + "message": "Warnung: Niemals jemanden deinen Private Key mitteilen. Jeder der im Besitz deines Private Keys ist, kann jegliches Guthaben deines Accounts stehlen." + }, + "privateNetwork": { + "message": "Privates Netzwerk" + }, + "qrCode": { + "message": "QR Code anzeigen" + }, + "readdToken": { + "message": "Du kannst diesen Token zukünftig wieder hinzufügen indem du in den Menüpunkt \"Token hinzufügen\" in den Einstellungen deines Accounts gehst." + }, + "readMore": { + "message": "Hier mehr erfahren." + }, + "readMore2": { + "message": "Mehr erfahren." + }, + "receive": { + "message": "Erhalten" + }, + "recipientAddress": { + "message": "Empfängeradresse" + }, + "refundAddress": { + "message": "Rückerstattungsadresse" + }, + "rejected": { + "message": "Abgelehnt" + }, + "resetAccount": { + "message": "Account zurücksetzten" + }, + "restoreFromSeed": { + "message": "Mit Hilfe der Seed Wörterfolge wiederherstellen." + }, + "restoreVault": { + "message": "Vault wiederherstellen" + }, + "required": { + "message": "Benötigt" + }, + "retryWithMoreGas": { + "message": "Versuche es hier mit einem höheren Gaspreis noch einmal" + }, + "walletSeed": { + "message": "Wallet Seed" + }, + "revealSeedWords": { + "message": "Seed Wörterfolge anzeigen" + }, + "revealSeedWordsWarning": { + "message": "Bitte niemals deine Seed Wörterfolge an einem öffentlichen Ort kenntlich machen. Mit diesen Wörtern können alle deine Accounts gestohlen werden." + }, + "revert": { + "message": "Zurück gehen" + }, + "rinkeby": { + "message": "Rinkeby Testnetzwerk" + }, + "ropsten": { + "message": "Ropsten Testnetzwerk" + }, + "currentRpc": { + "message": "Aktueller RPC" + }, + "connectingToMainnet": { + "message": "Verbinde zum Ethereum Hauptnetzwerk (Main Net)" + }, + "connectingToRopsten": { + "message": " Verbinde zum Ropsten Testnetzwerk" + }, + "connectingToKovan": { + "message": " Verbinde zum Kovan Testnetzwerk" + }, + "connectingToRinkeby": { + "message": " Verbinde zum Rinkeby Testnetzwerk" + }, + "connectingToUnknown": { + "message": "Verbinde zu einem unbekanntem Netzwerk" + }, + "sampleAccountName": { + "message": "Z.B. mein neuer Account", + "description": "Dem Benutzer helfen das Konzept des Hinzufügens eines menschlich lesbaren Namen für den Account hinzuzufügen" + }, + "save": { + "message": "Speichern" + }, + "saveAsFile": { + "message": "Als Datei speichern", + "description": "Prozess des Exportieren eines Accounts" + }, + "saveSeedAsFile": { + "message": "Seed Wörterfolge als Datei speichern" + }, + "search": { + "message": "Suche" + }, + "secretPhrase": { + "message": "Gib die 12 Wörter deiner geheimem Wörterfolge ein um deinen Vault wiederherzustellen." + }, + "newPassword8Chars": { + "message": "Neues Passwort (min. 8 Zeichen)" + }, + "seedPhraseReq": { + "message": "Seed Wörterfolgen bestehen aus 12 Wörtern" + }, + "select": { + "message": "Auswählen" + }, + "selectCurrency": { + "message": "Währung auswählen" + }, + "selectService": { + "message": "Service auswählen" + }, + "selectType": { + "message": "Typ auswählen" + }, + "send": { + "message": "Senden" + }, + "sendETH": { + "message": "ETH senden" + }, + "sendTokens": { + "message": "Token senden" + }, + "onlySendToEtherAddress": { + "message": "ETH nur zu einer Ethereum Adresse senden." + }, + "sendTokensAnywhere": { + "message": "Token zu einer beliebigen Person mit einem Ethereumaccount senden" + }, + "settings": { + "message": "Einstellungen" + }, + "info": { + "message": "Info" + }, + "shapeshiftBuy": { + "message": "Mit Shapeshift kaufen" + }, + "showPrivateKeys": { + "message": "Private Keys anzeigen" + }, + "showQRCode": { + "message": "QR Code anzeigen" + }, + "sign": { + "message": "Unterschreiben" + }, + "signed": { + "message": "Unterschrieben" + }, + "signMessage": { + "message": "Nachricht unterschreiben" + }, + "signNotice": { + "message": "Das Unterschreiben dieser Nachricht kann gefährliche Nebeneffekte haben. Bitte nur Nachrichten von Webseiten unterschreiben denen du deinen vollständigen Account anvertrauen würdest. Diese gefährliche Variante wird in zukünftigen Versionen entfernt werden." + }, + "sigRequest": { + "message": "Unterschriftsanfrage" + }, + "sigRequested": { + "message": "Unterschrift angefragt" + }, + "spaceBetween": { + "message": "Es darf nur ein Leerzeichen zwischen den Wörtern sein" + }, + "status": { + "message": "Status" + }, + "stateLogs": { + "message": "Statelogs" + }, + "stateLogsDescription": { + "message": "Statelogs zeigen die Public Adresse und die gesendeten Transaktionen deines Accounts." + }, + "stateLogError": { + "message": "Fehler beim Abfragen der Statelogs." + }, + "submit": { + "message": "Einreichen" + }, + "submitted": { + "message": "Eingereicht" + }, + "supportCenter": { + "message": "Gehe zu unserem Support Center" + }, + "symbolBetweenZeroTen": { + "message": "Das Symbol muss zwischen 0 und 10 Zeichen haben." + }, + "takesTooLong": { + "message": "Dauert es zu lang?" + }, + "terms": { + "message": "Nutzungsbedingungen" + }, + "testFaucet": { + "message": "Testfaucet" + }, + "to": { + "message": "An:" + }, + "toETHviaShapeShift": { + "message": "$1 an ETH via ShapeShift", + "description": "Das System wird den Einzahlungstyp im Beginn der Nachricht eintragen" + }, + "tokenAddress": { + "message": "Tokenadresse" + }, + "tokenAlreadyAdded": { + "message": "Der Token wurde bereits hinzugefügt." + }, + "tokenBalance": { + "message": "Dein Tokenguthaben beträgt:" + }, + "tokenSelection": { + "message": "Suche nach Token oder wähle aus einer Liste der beliebtesten Token aus." + }, + "tokenSymbol": { + "message": "Tokensymbol" + }, + "tokenWarning1": { + "message": "Behalte die Token die du mit deinem MetaMask Account gekauft hast im Auge. Wenn du Token mit einem anderen Account gekauft hast, werden diese hier nicht angezeigt." + }, + "total": { + "message": "Gesamt" + }, + "transactions": { + "message": "Transaktionen" + }, + "transactionMemo": { + "message": "Transaktionsmemo (optional)" + }, + "transactionNumber": { + "message": "Transaktionsnummer" + }, + "transfers": { + "message": "Transfers" + }, + "troubleTokenBalances": { + "message": "Wir haben Schwierigkeiten dein Tokenguthaben zu laden. Du kannst es hier anzeigen lassen", + "description": "Gefolgt von einem Link (hier) um die Tokenguthaben anzuzeigen" + }, + "twelveWords": { + "message": "Diese 12 Wörter stellen die einzige Möglichkeit dar deinen MetaMask Account wiederherzustellen. Speichere sie daher an einem sicheren und geheimen Ort." + }, + "typePassword": { + "message": "Passwort eingeben" + }, + "uiWelcome": { + "message": "Willkommen zur neuen Oberfläche (Beta)" + }, + "uiWelcomeMessage": { + "message": "Du verwendest nun die neue Metamask Oberfläche. Schau dich um, teste die neuen Features wie z.B. das Senden von Token und lass es uns wissen falls du irgendwelche Probleme hast." + }, + "unapproved": { + "message": "Nicht genehmigt" + }, + "unavailable": { + "message": "Nicht verfügbar" + }, + "unknown": { + "message": "Unbekannt" + }, + "unknownNetwork": { + "message": "Unbekanntes privates Netzwerk" + }, + "uriErrorMsg": { + "message": "URIs benötigen die korrekten HTTP/HTTPS Präfixe." + }, + "unknownNetworkId": { + "message": "Unbekannte Netzwerk ID" + }, + "usaOnly": { + "message": "Nur USA ", + "description": "Diese Börse ist nur für Einwohner der USA verfügbar" + }, + "usedByClients": { + "message": "Verwendet von einer Reihe verschiedenen Kunden" + }, + "useOldUI": { + "message": "Alte Oberfläche verwenden" + }, + "validFileImport": { + "message": "Du musst eine gültige Datei für den Import auswählen." + }, + "vaultCreated": { + "message": "Vault erstellt" + }, + "viewAccount": { + "message": " Account einsehen" + }, + "visitWebSite": { + "message": "Gehe zu unsere Webseite" + }, + "warning": { + "message": "Warnung" + }, + "welcomeBeta": { + "message": "Willkommen zu MetaMask Beta" + }, + "whatsThis": { + "message": "Was ist das?" + }, + "yourSigRequested": { + "message": "Deine Unterschrift wird angefordert" + }, + "youSign": { + "message": "Du unterschreibst" + } +} diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 35a360c84..c64b7248b 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -37,6 +37,9 @@ "message": "MetaMask", "description": "The name of the application" }, + "approved": { + "message": "Approved" + }, "attemptingConnect": { "message": "Attempting to connect to blockchain." }, @@ -83,6 +86,9 @@ "buyCoinbaseExplainer": { "message": "Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin." }, + "ok": { + "message": "Ok" + }, "cancel": { "message": "Cancel" }, @@ -95,6 +101,9 @@ "confirm": { "message": "Confirm" }, + "confirmed": { + "message": "Confirmed" + }, "confirmContract": { "message": "Confirm Contract" }, @@ -226,6 +235,9 @@ "downloadStatelogs": { "message": "Download State Logs" }, + "dropped": { + "message": "Dropped" + }, "edit": { "message": "Edit" }, @@ -244,6 +256,12 @@ "enterPasswordConfirm": { "message": "Enter your password to confirm" }, + "passwordNotLongEnough": { + "message": "Password not long enough" + }, + "passwordsDontMatch": { + "message": "Passwords Don't Match" + }, "etherscanView": { "message": "View account on Etherscan" }, @@ -403,6 +421,9 @@ "knowledgeDataBase": { "message": "Visit our Knowledge Base" }, + "max": { + "message": "Max" + }, "lessThanMax": { "message": "must be less than or equal to $1.", "description": "helper for inputting hex as decimal input" @@ -410,6 +431,9 @@ "likeToAddTokens": { "message": "Would you like to add these tokens?" }, + "links": { + "message": "Links" + }, "limit": { "message": "Limit" }, @@ -583,12 +607,18 @@ "restoreFromSeed": { "message": "Restore from seed phrase" }, + "restoreVault": { + "message": "Restore Vault" + }, "required": { "message": "Required" }, "retryWithMoreGas": { "message": "Retry with a higher gas price here" }, + "walletSeed": { + "message": "Wallet Seed" + }, "revealSeedWords": { "message": "Reveal Seed Words" }, @@ -604,6 +634,24 @@ "ropsten": { "message": "Ropsten Test Network" }, + "currentRpc": { + "message": "Current RPC" + }, + "connectingToMainnet": { + "message": "Connecting to Main Ethereum Network" + }, + "connectingToRopsten": { + "message": "Connecting to Ropsten Test Network" + }, + "connectingToKovan": { + "message": "Connecting to Kovan Test Network" + }, + "connectingToRinkeby": { + "message": "Connecting to Rinkeby Test Network" + }, + "connectingToUnknown": { + "message": "Connecting to Unknown Network" + }, "sampleAccountName": { "message": "E.g. My new account", "description": "Help user understand concept of adding a human-readable name to their account" @@ -624,6 +672,9 @@ "secretPhrase": { "message": "Enter your secret twelve word phrase here to restore your vault." }, + "newPassword8Chars": { + "message": "New Password (min 8 chars)" + }, "seedPhraseReq": { "message": "seed phrases are 12 words long" }, @@ -648,12 +699,18 @@ "sendTokens": { "message": "Send Tokens" }, + "onlySendToEtherAddress": { + "message": "Only send ETH to an Ethereum address." + }, "sendTokensAnywhere": { "message": "Send Tokens to anyone with an Ethereum account" }, "settings": { "message": "Settings" }, + "info": { + "message": "Info" + }, "shapeshiftBuy": { "message": "Buy with Shapeshift" }, @@ -666,6 +723,9 @@ "sign": { "message": "Sign" }, + "signed": { + "message": "Signed" + }, "signMessage": { "message": "Sign Message" }, @@ -690,9 +750,15 @@ "stateLogsDescription": { "message": "State logs contain your public account addresses and sent transactions." }, + "stateLogError": { + "message": "Error in retrieving state logs." + }, "submit": { "message": "Submit" }, + "submitted": { + "message": "Submitted" + }, "supportCenter": { "message": "Visit our Support Center" }, @@ -709,7 +775,7 @@ "message": "Test Faucet" }, "to": { - "message": "To" + "message": "To: " }, "toETHviaShapeShift": { "message": "$1 to ETH via ShapeShift", @@ -764,6 +830,9 @@ "uiWelcomeMessage": { "message": "You are now using the new Metamask UI. Take a look around, try out new features like sending tokens, and let us know if you have any issues." }, + "unapproved": { + "message": "Unapproved" + }, "unavailable": { "message": "Unavailable" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 78fc64dbf..fa28b09da 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -1,10 +1,888 @@ { + "accept": { + "message": "Aceptar" + }, + "account": { + "message": "Cuenta" + }, + "accountDetails": { + "message": "Detalles de la cuenta" + }, + "accountName": { + "message": "Nombre de la cuenta" + }, + "addCustomToken": { + "message": "Agregar token personalizados" + }, + "addToken": { + "message": "Agregar token" + }, + "addTokens": { + "message": "Agregar tokens" + }, + "address": { + "message": "Dirección" + }, + "amount": { + "message": "Cantidad" + }, + "amountPlusGas": { + "message": "Cantidad + Gas" + }, + "appDescription": { + "message": "Extensión del navegador para Ethereum", + "description": "La descripción de la aplicación" + }, "appName": { "message": "MetaMask", - "description": "The name of the application" + "description": "El nombre de la aplicación" }, - "appDescription": { - "message": "Administración de identidad en Ethereum", - "description": "The description of the application" + "approved": { + "message": "Aprobado" + }, + "attemptingConnect": { + "message": "Intentando conectar a la Blockchain" + }, + "attributions": { + "message": "Atribuciones" + }, + "available": { + "message": "Disponible" + }, + "back": { + "message": "Atrás" + }, + "balance": { + "message": "Saldo" + }, + "balanceIsInsufficientGas": { + "message": "Saldo de gas insuficiente" + }, + "balances": { + "message": "Tus saldos" + }, + "beta": { + "message": "BETA" + }, + "betweenMinAndMax": { + "message": "Debe ser mayor o igual a $1 y menor o igual a $2", + "description": "helper para ingresar hex como un ingreso decimal" + }, + "blockiesIdenticon": { + "message": "Usar Blockies Identicon (Iconos)" + }, + "borrowDharma": { + "message": "Pedir prestado con Dharma (Beta)" + }, + "builtInCalifornia": { + "message": "Metamask fue diseñado y construido en California" + }, + "buy": { + "message": "Comprar" + }, + "buyCoinbase": { + "message": "Comprar en Coinbase" + }, + "buyCoinbaseExplainer": { + "message": "Coinbase es la plataforma global más popular para comprar y vender Bitcoin, Ethereum y Litecoin" + }, + "cancel": { + "message": "Cancelar" + }, + "classicInterface": { + "message": "Usar interfaz clásica" + }, + "clickCopy": { + "message": "Click para copiar" + }, + "confirm": { + "message": "Confirmar" + }, + "confirmContract": { + "message": "Confirmar contrato" + }, + "confirmed": { + "message": "Confirmado" + }, + "confirmPassword": { + "message": "Confirmar contraseña" + }, + "confirmTransaction": { + "message": "Confirmar transacción" + }, + "connectingToMainnet": { + "message": "Conectando a la red principal de Ethereum (Main Net)" + }, + "connectingToRopsten": { + "message": "Conectando a la red de test Ropsten" + }, + "connectingToKovan": { + "message": "Conectando a la red de test Kovan" + }, + "connectingToRinkeby": { + "message": "Conectando a la red de test Rinkeby" + }, + "connectingToUnknown": { + "message": "Conectando a una red desconocida" + }, + "continue": { + "message": "Continuar" + }, + "continueToCoinbase": { + "message": "Continuar a Coinbase" + }, + "contractDeployment": { + "message": "Desplegar (Deploy) contrato" + }, + "conversionProgress": { + "message": "Conversión en progreso" + }, + "copiedButton": { + "message": "Copiado" + }, + "copiedClipboard": { + "message": "Copiado al portapapeles" + }, + "copiedExclamation": { + "message": "¡Copiado!" + }, + "copiedSafe": { + "message": "Ya lo guardé en un lugar seguro" + }, + "copy": { + "message": "Copiar" + }, + "copyButton": { + "message": " Copiar " + }, + "copyPrivateKey": { + "message": "Ésta es tu llave privada (haz click para copiar)" + }, + "copyToClipboard": { + "message": "Copiar al portapapeles" + }, + "create": { + "message": "Crear" + }, + "createAccount": { + "message": "Crear cuenta" + }, + "createDen": { + "message": "Crear" + }, + "crypto": { + "message": "Crypto", + "description": "Tipo de cambio (criptomonedas)" + }, + "currentConversion": { + "message": "Conversión actual" + }, + "currentNetwork": { + "message": "Red actual" + }, + "currentRpc": { + "message": "RPC actual" + }, + "customGas": { + "message": "Personalizar gas" + }, + "customRPC": { + "message": "RPC personalizado" + }, + "customize": { + "message": "Personalizar" + }, + "decimal": { + "message": "Decimales de precisión" + }, + "decimalsMustZerotoTen": { + "message": "Los decimales deben ser al menos 0 y no más de 36" + }, + "defaultNetwork": { + "message": "La red por defecto para las transacciones de Ether es MainNet (red principal)" + }, + "denExplainer": { + "message": "El DEN es tu contraseña encriptada almacenada dentro de MetaMask" + }, + "deposit": { + "message": "Depositar" + }, + "depositBTC": { + "message": "Deposita tus BTC a la dirección de abajo:" + }, + "depositCoin": { + "message": "Deposita tu $1 a la dirección de abajo", + "description": "Informa al usuario que moneda ha elegido para depositar en shapeshift" + }, + "depositEth": { + "message": "Depositar Ether" + }, + "depositEther": { + "message": "Depositar Ether" + }, + "depositFiat": { + "message": "Depositar con fiat (divisa nacional)" + }, + "depositFromAccount": { + "message": "Depositar con otra cuenta" + }, + "depositShapeShift": { + "message": "Depositar con ShapeShift" + }, + "depositShapeShiftExplainer": { + "message": "Si posees otras criptomonedas, puedes intercambiar y depositar Ether directamente en tu billetera de MetaMask. No necesitas tener una cuenta." + }, + "details": { + "message": "Detalles" + }, + "directDeposit": { + "message": "Depósito directo" + }, + "directDepositEther": { + "message": "Depositar Ether directamente" + }, + "directDepositEtherExplainer": { + "message": "Si posees Ether, la forma más rápida de transferirlo a tu nueva billetera es depositándolo directamente" + }, + "done": { + "message": "Completo" + }, + "downloadStatelogs": { + "message": "Descargar logs de estado" + }, + "dropped": { + "message": "Caído" + }, + "edit": { + "message": "Editar" + }, + "editAccountName": { + "message": "Editar el nombre de la cuenta" + }, + "emailUs": { + "message": "¡Envíanos un correo!" + }, + "encryptNewDen": { + "message": "Encriptar tu nuevo DEN" + }, + "enterPassword": { + "message": "Ingresa contraseña" + }, + "enterPasswordConfirm": { + "message": "Ingresa tu contraseña para confirmar" + }, + "etherscanView": { + "message": "Ver la cuenta en Etherscan" + }, + "exchangeRate": { + "message": "Tipo de cambio" + }, + "exportPrivateKey": { + "message": "Exportar llave privada" + }, + "exportPrivateKeyWarning": { + "message": "Exportar llaves privadas bajo TU PROPIO riesgo" + }, + "failed": { + "message": "Fallo" + }, + "fiat": { + "message": "FIAT", + "description": "Tipo de cambio" + }, + "fileImportFail": { + "message": "¿La importación no funcionó? ¡Haz click aquí!", + "description": "Ayuda al usuario a importar su cuenta desde un archivo JSON" + }, + "followTwitter": { + "message": "Síguenos en Twitter" + }, + "from": { + "message": "De:" + }, + "fromShapeShift": { + "message": "De ShapeShift" + }, + "fromToSame": { + "message": "La dirección de origen y destino no pueden ser la misma" + }, + "gas": { + "message": "Gas", + "description": "Indicación pequeña del costo de gas" + }, + "gasFee": { + "message": "Comisión de gas" + }, + "gasLimit": { + "message": "Límite de gas" + }, + "gasLimitCalculation": { + "message": "Calculamos el límite de gas sugerido en función de las tasas de éxito de la red" + }, + "gasLimitRequired": { + "message": "Límite de gas requerido" + }, + "gasLimitTooLow": { + "message": "El límite de gas debe ser de al menos 21000" + }, + "gasPrice": { + "message": "Precio del Gas (GWEI)" + }, + "gasPriceCalculation": { + "message": "Calculamos los precios sugeridos del gas en función de las tasas de éxito de la red" + }, + "gasPriceRequired": { + "message": "Precio del gas requerido" + }, + "generatingSeed": { + "message": "Generando semilla..." + }, + "getEther": { + "message": "Conseguir Ether" + }, + "getEtherFromFaucet": { + "message": "Obtenga Ether de un faucet (grifo) por $1", + "description": "Muestra el nombre de la red para el faucet (grifo) de Ether" + }, + "greaterThanMin": { + "message": "Debe ser mayor o igual a $1", + "description": "helper para ingresar hex como entrada decimal" + }, + "here": { + "message": "Aquí", + "description": "como en -haz click aquí- para más información" + }, + "hereList": { + "message": "¡¡¡Aquí está una lista!!!" + }, + "hide": { + "message": "Ocultar" + }, + "hideToken": { + "message": "Ocultar token" + }, + "hideTokenPrompt": { + "message": "¿Ocultar token?" + }, + "holdEther": { + "message": "Te permite mantener tus ether y tokens, así como puente para aplicaciones descentralizadas" + }, + "howToDeposit": { + "message": "¿Cómo te gustaria depositar Ether?" + }, + "import": { + "message": "Importar", + "description": "Botón para importar una cuenta desde un archivo seleccionado" + }, + "importAccount": { + "message": "Importar cuenta" + }, + "importAnAccount": { + "message": "Importar una cuenta" + }, + "importDen": { + "message": "Importar DEN existente" + }, + "imported": { + "message": "Importado", + "description": "Estado que muestra que una cuenta ha sido completamente cargada en el llavero" + }, + "importAccountMsg": { + "message": "Las cuentas importadas no serán asociadas con tu cuenta original creada con tu MetaMask. Aprende más acerca de importar cuentas." + }, + "info": { + "message": "Información" + }, + "infoHelp": { + "message": "Informacion y ayuda" + }, + "insufficientFunds": { + "message": "Fondos insuficientes" + }, + "insufficientTokens": { + "message": "Tokens insuficientes" + }, + "invalidAddress": { + "message": "Dirección inválida" + }, + "invalidAddressRecipient": { + "message": "Dirección del destinatario invalida" + }, + "invalidGasParams": { + "message": "Parametros de gas inválidos" + }, + "invalidInput": { + "message": "Entrada inválida" + }, + "invalidRPC": { + "message": "Invalida URL del RPC" + }, + "invalidRequest": { + "message": "Petición inválida" + }, + "jsonFail": { + "message": "Algo falló. Asegúrate que tu JSON tiene el formato correcto" + }, + "jsonFile": { + "message": "Archivo JSON", + "description": "Formato para importar una cuenta" + }, + "knowledgeDataBase": { + "message": "Visita nuestra base de conocimiento" + }, + "kovan": { + "message": "Red de pruebas Kovan" + }, + "lessThanMax": { + "message": "Debe ser menor o igual a $1", + "description": "Helper para ingresar hex como decimal" + }, + "likeToAddTokens": { + "message": "¿Te gustaría agregar estos tokens?" + }, + "limit": { + "message": "Límite" + }, + "links": { + "message": "Enlaces" + }, + "loading": { + "message": "Cargando..." + }, + "loadingTokens": { + "message": "Cargando tokens..." + }, + "localhost": { + "message": "Localhost 8545" + }, + "login": { + "message": "Ingresar" + }, + "logout": { + "message": "Cerrar sesión" + }, + "loose": { + "message": "Suelto" + }, + "loweCaseWords": { + "message": "las frases semilla sólo pueden tener minúsculas" + }, + "mainnet": { + "message": "Red principal de Ethereum (Main Net)" + }, + "max": { + "message": "Max" + }, + "message": { + "message": "Mensaje" + }, + "metamaskDescription": { + "message": "MetaMask es una identidad segura en Ethereum" + }, + "min": { + "message": "Mínimo" + }, + "mustSelectOne": { + "message": "Debe seleccionar al menos un (1) token" + }, + "myAccounts": { + "message": "Mis cuentas" + }, + "needEtherInWallet": { + "message": "Para interactuar con una aplicación descentralizada usando MetaMask, necesitas tener Ether en tu billetera" + }, + "needImportFile": { + "message": "Debes seleccionar un archivo para importar", + "description": "El usuario está importando una cuenta y necesita agregar un archivo para continuar" + }, + "needImportPassword": { + "message": "Debes ingresar una contraseña para el archivo seleccionado", + "description": "Contraseña y archivo necesarios para importar una cuenta" + }, + "negativeETH": { + "message": "No se pueden mandar cantidades negativas de ETH" + }, + "networks": { + "message": "Redes" + }, + "newAccount": { + "message": "Nueva cuenta" + }, + "newAccountNumberName": { + "message": "Cuenta $1", + "description": "Nombre por defecto de la próxima cuenta a ser creada o pantalla de creación de cuenta" + }, + "newContract": { + "message": "Nuevo contrato" + }, + "newPassword": { + "message": "Nueva contraseña (mínimo [8] caracteres)" + }, + "newPassword8Chars": { + "message": "Nueva contraseña (mínimo [8] caracteres)" + }, + "newRPC": { + "message": "Nueva URL del RPC" + }, + "newRecipient": { + "message": "Nuevo destinatario" + }, + "next": { + "message": "Siguiente" + }, + "noAddressForName": { + "message": "No se ha establecido ninguna dirección para este nombre" + }, + "noDeposits": { + "message": "No hay depósitos recibidos" + }, + "noTransactionHistory": { + "message": "Sin historial de transacciones" + }, + "noTransactions": { + "message": "Sin transacciones" + }, + "notStarted": { + "message": "Sin iniciar" + }, + "ok": { + "message": "Ok" + }, + "oldUI": { + "message": "Antigua UI (Interfaz de Usuario)" + }, + "oldUIMessage": { + "message": "Regresaste a la antigua UI (Interfaz de Usuario). Puedes regresar a la nueva UI mediante la opcion en la barra desplegable del menu de arriba a la derecha." + }, + "onlySendToEtherAddress": { + "message": "Sólo envía a una dirección de Ethereum" + }, + "or": { + "message": "o", + "description": "opción entre crear o importar una cuenta" + }, + "passwordCorrect": { + "message": "Asegurate que tu contraseña es correcta" + }, + "passwordMismatch": { + "message": "La contraseña no coincide", + "description": "En el proceso de creación de contraseña, los dos campos de contraseña no coincidieron" + }, + "passwordNotLongEnough": { + "message": "La contraseña no es lo suficientemente larga" + }, + "passwordsDontMatch": { + "message": "Las contraseñas no coinciden" + }, + "passwordShort": { + "message": "La contraseña no es lo suficientemente larga", + "description": "En el proceso de creación de contraseña, esta no es lo suficientemente larga para ser segura" + }, + "pastePrivateKey": { + "message": "Pega tu llave privada aqui", + "description": "Para importar una cuenta desde una llave privada" + }, + "pasteSeed": { + "message": "¡Pega tu frase semilla aquí!" + }, + "personalAddressDetected": { + "message": "Dirección personal detectada. Ingresa la dirección del contrato del token" + }, + "pleaseReviewTransaction": { + "message": "Por favor, revisa tu transaccion" + }, + "privacyMsg": { + "message": "Política de privacidad" + }, + "privateKey": { + "message": "Llave privada", + "description": "Selecciona este tupo de archivo para importar una cuenta" + }, + "privateKeyWarning": { + "message": "Advertencia: NUNCA reveles esta clave. Cualquier persona con tus claves privadas puede robar los activos retenidos en tu cuenta" + }, + "privateNetwork": { + "message": "Red privada" + }, + "qrCode": { + "message": "Mostrar codigo QR" + }, + "readMore": { + "message": "Leer más aquí" + }, + "readMore2": { + "message": "Leer más" + }, + "readdToken": { + "message": "Puedes volver a agregar este token en el futuro pinchando sobre 'Agregar token' en el menú de opciones de tu cuenta" + }, + "receive": { + "message": "Recibir" + }, + "recipientAddress": { + "message": "Dirección del receptor" + }, + "refundAddress": { + "message": "Tu dirección de reembolso" + }, + "rejected": { + "message": "Rechazado" + }, + "required": { + "message": "Requerido" + }, + "resetAccount": { + "message": "Reiniciar cuenta" + }, + "restoreFromSeed": { + "message": "Restaurar desde semilla" + }, + "restoreVault": { + "message": "Restaurar Bóveda" + }, + "retryWithMoreGas": { + "message": "Vuelva a intentar con un precio de gas más alto aquí" + }, + "revealSeedWords": { + "message": "Revelar palabras de semilla" + }, + "revealSeedWordsWarning": { + "message": "¡No recuperes tu semilla en un lugar pública! Esas palabras pueden ser usadas para robarte todas tus cuentas" + }, + "revert": { + "message": "Revertir" + }, + "rinkeby": { + "message": "Red privada Rinkeby" + }, + "ropsten": { + "message": "Red privada Ropsten" + }, + "sampleAccountName": { + "message": "P.ej. Mi nueva cuenta", + "description": "Ayuda al usuario a entender el concepto de agregar un nombre, leíble por humanos, a su cuenta" + }, + "save": { + "message": "Guardar" + }, + "saveAsFile": { + "message": "Guardar como archivo", + "description": "Proceso de exportación de cuenta" + }, + "saveSeedAsFile": { + "message": "Guardar la semilla como archivo" + }, + "search": { + "message": "Buscar" + }, + "secretPhrase": { + "message": "Ingresa tu frase de doce (12) palabras para restaurar tu bóveda" + }, + "seedPhraseReq": { + "message": "las frases semilla tienen doce (12) palabras de largo" + }, + "select": { + "message": "Seleccionar" + }, + "selectCurrency": { + "message": "Seleccionar moneda" + }, + "selectService": { + "message": "Seleccionar servicio" + }, + "selectType": { + "message": "Seleccionar tipo" + }, + "send": { + "message": "Enviar" + }, + "sendETH": { + "message": "Enviar Ether" + }, + "sendTokens": { + "message": "Enviar tokens" + }, + "sendTokensAnywhere": { + "message": "Enviar tokens a cualquiera con una cuenta de Ethereum" + }, + "settings": { + "message": "Configuración" + }, + "shapeshiftBuy": { + "message": "Comprar con ShapeShift" + }, + "showPrivateKeys": { + "message": "Mostrar llaves privadas" + }, + "showQRCode": { + "message": "Mostrar codigo QR" + }, + "sigRequest": { + "message": "Solicitud de firma" + }, + "sigRequested": { + "message": "Firma solicitada" + }, + "sign": { + "message": "Firmar" + }, + "signed": { + "message": "Firmado" + }, + "signMessage": { + "message": "Firmar mensaje" + }, + "signNotice": { + "message": "Firmar este mensaje puede tener\n efectos secundarios peligrosos. Firma sólo\nmensajes desde sitios a los que estés plenamente dispuesto a confiar tu cuenta.\nEste método peligroso va a ser \neliminado en una version futura." + }, + "spaceBetween": { + "message": "Sólo puede haber un espacio entre las palabras" + }, + "stateLogs": { + "message": "Logs de estado" + }, + "stateLogsDescription": { + "message": "Los logs de estado contienen tus direcciones de cuentas públicas y transacciones envíadas" + }, + "stateLogError": { + "message": "Error en la recogida de logs de estado" + }, + "status": { + "message": "Estado" + }, + "submit": { + "message": "Enviar" + }, + "submitted": { + "message": "Enviado" + }, + "supportCenter": { + "message": "Visita nuestro centro de atención" + }, + "symbolBetweenZeroTen": { + "message": "Símbolo debe ser entre 0 y 10 caracteres" + }, + "takesTooLong": { + "message": "¿Está tardando demasiado?" + }, + "terms": { + "message": "Términos de uso" + }, + "testFaucet": { + "message": "Probar Faucet" + }, + "to": { + "message": "Para:" + }, + "toETHviaShapeShift": { + "message": "$1 a ETH via ShapeShift", + "description": "el sistema llenará el tipo de depósito al principio del mensaje" + }, + "tokenAddress": { + "message": "Dirección del token" + }, + "tokenAlreadyAdded": { + "message": "El token está actualmente agregado" + }, + "tokenBalance": { + "message": "Tu balance de tokens es:" + }, + "tokenSelection": { + "message": "Busca tokens o selecciónalo de nuestra lista de tokens populares" + }, + "tokenSymbol": { + "message": "Símbolo del token" + }, + "tokenWarning1": { + "message": "Mantén un registro de los tokens que has comprado con tu cuenta de MetaMask. Si compraste tokens usando una cuenta diferente, esos tokens no aparecerán aquí." + }, + "total": { + "message": "Total" + }, + "transactionMemo": { + "message": "Memo de transacción (opcional)" + }, + "transactionNumber": { + "message": "Número de transacción" + }, + "transactions": { + "message": "Transacciones" + }, + "transfers": { + "message": "Transferencias" + }, + "troubleTokenBalances": { + "message": "Tuvimos problemas para cargar tus saldos de tokens. Puedes verlos ", + "description": "Seguidos por un enlace (aquí) para ver los saldos de token" + }, + "twelveWords": { + "message": "Estas 12 palabras son la única forma de restablecer tus cuentas de MetaMask. \nGuárdalas en un lugar seguro y secreto." + }, + "typePassword": { + "message": "Escribe tu contraseña" + }, + "uiWelcome": { + "message": "Bienvenido a la nueva UI (Beta)" + }, + "uiWelcomeMessage": { + "message": "Estás usando la nueva UI de MetaMask. Echa un vistazo alrededor, prueba las nuevas características, tales como mandar tokens, y háznos saber si tienes algún problema" + }, + "unavailable": { + "message": "No disponible" + }, + "unapproved": { + "message": "No aprobado" + }, + "unknown": { + "message": "Desconocido (a)" + }, + "unknownNetwork": { + "message": "Red privada desconocida" + }, + "unknownNetworkId": { + "message": "ID (identidad) de red desconocida" + }, + "uriErrorMsg": { + "message": "URI necesita el prefijo HTTP/HTTPS apropiado" + }, + "usaOnly": { + "message": "Sólo USA (Estados Unidos)", + "description": "El uso de este exchange (casa de cambio) está limitado a las personas dentro de los Estados Unidos de América" + }, + "useOldUI": { + "message": "Usar UI antigua" + }, + "usedByClients": { + "message": "Utilizado por una variedad de clientes diferentes" + }, + "validFileImport": { + "message": "Debes selecionar un archivo valido para importar" + }, + "vaultCreated": { + "message": "Bóveda creada" + }, + "viewAccount": { + "message": "Mirar cuenta" + }, + "visitWebSite": { + "message": "Visita nuestro sitio web" + }, + "walletSeed": { + "message": "Semilla de la billetera" + }, + "warning": { + "message": "Advertencia" + }, + "welcomeBeta": { + "message": "Bienvenido a Metamask Beta" + }, + "whatsThis": { + "message": "¿Qué es esto?" + }, + "youSign": { + "message": "Usted está firmando" + }, + "yourSigRequested": { + "message": "Tu firma ya fue solicitada" } } diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json deleted file mode 100644 index 78fc64dbf..000000000 --- a/app/_locales/es_419/messages.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "appName": { - "message": "MetaMask", - "description": "The name of the application" - }, - "appDescription": { - "message": "Administración de identidad en Ethereum", - "description": "The description of the application" - } -} diff --git a/app/_locales/hn/messages.json b/app/_locales/hn/messages.json new file mode 100644 index 000000000..3703faa13 --- /dev/null +++ b/app/_locales/hn/messages.json @@ -0,0 +1,819 @@ +{ + "accept": { + "message": "स्वीकार करें" + }, + "account": { + "message": "खाता" + }, + "accountDetails": { + "message": "खाता विवरण" + }, + "accountName": { + "message": "खाता का नाम" + }, + "address": { + "message": "खाते का पता" + }, + "addCustomToken": { + "message": "कस्टम टोकन जोड़ें" + }, + "addToken": { + "message": "टोकन जोड़ें" + }, + "addTokens": { + "message": "टोकनो को जोड़ें" + }, + "amount": { + "message": "राशि" + }, + "amountPlusGas": { + "message": "राशि + गैस" + }, + "appDescription": { + "message": "एथरेम ब्राउज़र एक्सटेंशन", + "description": "आवेदन का विवरण" + }, + "appName": { + "message": "मेटामास्क/MetaMask", + "description": "एप्लिकेशन का नाम" + }, + "attemptingConnect": { + "message": "ब्लॉकचैन से कनेक्ट करने का प्रयास करना होगा।सब्र करे।" + }, + "attributions": { + "message": "एट्रिब्यूशन" + }, + "available": { + "message": "एट्रिब्यूशन उपलब्ध ह्ने" + }, + "back": { + "message": "वापस" + }, + "balance": { + "message": "उपलब्ध बैलेंस।" + }, + "balances": { + "message": "ापके उपलब्ध बैलेंस" + }, + "balanceIsInsufficientGas": { + "message": "वर्तमान गैस कुल के लिए अपर्याप्त शेष" + }, + "beta": { + "message": "BETA/बीटा" + }, + "betweenMinAndMax": { + "message": "$1 के बराबर या ज्यदा या, $2 के बराबर या कम होना चाहिए।", + "description": "हेक्स इनपुट के लिए दशमलव इनपुट के रूप में सहायक" + }, + "blockiesIdenticon": { + "message": "ब्लॉकीज पहचान का उपयोग करें" + }, + "borrowDharma": { + "message": "धर्मा (बीटा) से / के साथ उधार लें" + }, + "builtInCalifornia": { + "message": "मेटामास्क कैलिफ़ोर्निया में डिज़ाइन और बनाया गया है।" + }, + "buy": { + "message": "खरीदें" + }, + "buyCoinbase": { + "message": "कॉनबेस पर खरीदें" + }, + "buyCoinbaseExplainer": { + "message": "बिल्टकोइन, एथरेम और लाइटकोइन खरीदने और बेचने के लिए दुनिया का सबसे लोकप्रिय तरीका Coinbase है।" + }, + "cancel": { + "message": "रद्द करें" + }, + "classicInterface": { + "message": "क्लासिक इंटरफ़ेस का उपयोग क" + }, + "clickCopy": { + "message": "कॉपी करने के लिए क्लिक करें" + }, + "confirm": { + "message": "पुष्टि करें" + }, + "confirmContract": { + "message": "अनुबंध की पुष्टि करें" + }, + "confirmPassword": { + "message": "पासवर्ड की पुष्टि करें" + }, + "confirmTransaction": { + "message": "लेनदेन की पुष्टि करें" + }, + "continue": { + "message": "जारी रखें" + }, + "continueToCoinbase": { + "message": "कॉ्ोनबेस को ब्हेजना जारी रखें" + }, + "contractDeployment": { + "message": "अनुबंध परिनियोजन व तैनाती" + }, + "conversionProgress": { + "message": "रूपांतरण प्रगति में हे।" + }, + "copiedButton": { + "message": "कॉपी किया गया" + }, + "copiedClipboard": { + "message": "क्लिपबोर्ड पर कॉपी किया गया" + }, + "copiedExclamation": { + "message": "कॉपी कर दिया गया!" + }, + "copiedSafe": { + "message": "मैंने इसे कहीं सुरक्षित कॉपी कर दिया है" + }, + "copy": { + "message": "कॉपी / प्रतिलिपि कर्रे" + }, + "copyToClipboard": { + "message": "क्लिपबोर्ड पर कॉपी करें" + }, + "copyButton": { + "message": " कॉपी / प्रतिलिपि कर्रे [बत्तन] " + }, + "copyPrivateKey": { + "message": "यह आपकी निजी कुंजी है (कॉपी करने के लिए क्लिक करें)।" + }, + "create": { + "message": "बनाएं" + }, + "createAccount": { + "message": "खाता बनाएं" + }, + "createDen": { + "message": "डेन बनाएं" + }, + "crypto": { + "message": "क्रिप्टो", + "description": "एक्सचेंज टाइप (क्रिप्टोक्यूचरस)" + }, + "currentConversion": { + "message": "वर्तमान रूपांतरण" + }, + "currentNetwork": { + "message": "वर्तमान नेटवर्क" + }, + "customGas": { + "message": "अनुकूलित करें गैस" + }, + "customize": { + "message": "अनुकूलित करें" + }, + "customRPC": { + "message": "कस्टम RPC" + }, + "decimalsMustZerotoTen": { + "message": "दशमलव कम से कम 0 होनी चाहिए, और 36 से अधिक नहीं होनी चाहिए।" + }, + "decimal": { + "message": "दशमलव परिशुद्धता" + }, + "defaultNetwork": { + "message": "ईथर लेनदेन के लिए डिफ़ॉल्ट नेटवर्क मुख्य नेट है।" + }, + "denExplainer": { + "message": "आपका डेन मेटामास्क के भीतर आपका पासवर्ड-एन्क्रिप्टेड स्टोरेज है।" + }, + "deposit": { + "message": "जमा" + }, + "depositBTC": { + "message": "नीचे दिए गए पते पर अपना बीटीसी जमा करें:" + }, + "depositCoin": { + "message": "नीचे दिए गए पते पर अपना $1 जमा करें", + "description": "उपयोगकर्ता को बताता है कि उन्होंने सिक्का के साथ जमा करने के लिए किस सिक्का का चयन किया है" + }, + "depositEth": { + "message": "Eth जमाआर्थ" + }, + "depositEther": { + "message": "जमा - Ether" + }, + "depositFiat": { + "message": "फिएट के साथ जमा हो" + }, + "depositFromAccount": { + "message": "दूसरे खाते से जमा करें" + }, + "depositShapeShift": { + "message": "शेपशिप के साथ जमा करें" + }, + "depositShapeShiftExplainer": { + "message": "यदि आप अन्य क्रिप्टोकाउंटरज रखते हैं, तो आप सीधे मेटामास्क वॉलेट में ईथर को व्यापार और जमा कर सकते हैं। कोई खाता आवश्यक नहीं है।" + }, + "details": { + "message": "संदेश विवरण" + }, + "directDeposit": { + "message": "प्रत्यक्ष जमा" + }, + "directDepositEther": { + "message": "सीधे ईथर जमा करें" + }, + "directDepositEtherExplainer": { + "message": "यदि आपके पास पहले से कुछ ईथर है, तो सीधे जमा द्वारा अपने नए बटुए में ईथर प्राप्त करने का तेज़ तरीका है।" + }, + "done": { + "message": "संपन्न" + }, + "downloadStatelogs": { + "message": "राज्य लॉग डाउनलोड करें" + }, + "edit": { + "message": "संपादित करें" + }, + "editAccountName": { + "message": "खाता नाम संपादित करें" + }, + "emailUs": { + "message": "हमें ईमेल करें!" + }, + "encryptNewDen": { + "message": "अपना नया डेन एन्क्रिप्ट करें" + }, + "enterPassword": { + "message": "पासवर्ड दर्ज करें" + }, + "enterPasswordConfirm": { + "message": "पुष्टि करने के लिए अपना पासवर्ड दर्ज करें" + }, + "etherscanView": { + "message": "ईथरस्कैन पर खाता देखें" + }, + "exchangeRate": { + "message": "विनिमय दरै" + }, + "exportPrivateKey": { + "message": "निजी कुंजी निर्यात करें" + }, + "exportPrivateKeyWarning": { + "message": "अपने जोखिम पर निजी कुंजी निर्यात करें।" + }, + "failed": { + "message": "विफल" + }, + "fiat": { + "message": "FIAT एक्सचेंज टाइप", + "description": "एक्सचेंज FIAT टाइप" + }, + "fileImportFail": { + "message": "फ़ाइल आयात काम नहीं कर रहा है? यहां क्लिक करें!", + "description": "यूजर को अपने खाते को जे.एस.ौ.एन फ़ाइल से आयात करने में मदद करता है" + }, + "followTwitter": { + "message": "हमें ट्विटर पर अनुसरण करें" + }, + "from": { + "message": "की तरफ से - संदेश" + }, + "fromToSame": { + "message": "से और पता करने के लिए समान नहीं हो सकता" + }, + "fromShapeShift": { + "message": "सेशशशफ्ट का" + }, + "gas": { + "message": "गैस की लागत", + "description": "गैस की लागत का संक्षिप्त संकेत" + }, + "gasFee": { + "message": "गैस शुल्क" + }, + "gasLimit": { + "message": "गैस सीमा" + }, + "gasLimitCalculation": { + "message": "हम नेटवर्क की सफलता दर के आधार पर सुझाई गई गैस सीमा की गणना करते हैं।" + }, + "gasLimitRequired": { + "message": "गैस सीमा आवश्यक" + }, + "gasLimitTooLow": { + "message": "२१००० - गैस की सीमा कम से कम 21000 होनी चाहिए" + }, + "generatingSeed": { + "message": "सृजन बीज उत्पन्न ... उत्पन्न ...उत्पन्न..." + }, + "gasPrice": { + "message": "गैस मूल्य (जीडब्ल्यूईआई),(GWEI)" + }, + "gasPriceCalculation": { + "message": "हम नेटवर्क की सफलता दर के आधार पर सुझाए गए गैस की कीमतों की गणना करते हैं।" + }, + "gasPriceRequired": { + "message": "गैस की कीमत आवश्यक है" + }, + "getEther": { + "message": "ईथर प्राप्त करें" + }, + "getEtherFromFaucet": { + "message": "$1 के लिए एक नल से ईथर प्राप्त करें", + "description": "ईथर नल के लिए नेटवर्क नाम प्रदर्शित करता है" + }, + "greaterThanMin": { + "message": "$1 के बराबर या बराबर होना चाहिए।", + "description": "हेक्स इनपुट के लिए दशमलव इनपुट के रूप में सहायक" + }, + "here": { + "message": "यहां", + "description": "अधिक जानकारी के लिए यहां क्लिक करें- (परेशानी के साथ जाता है टोकनबैलेंस) (troubleTokenBalances)" + }, + "hereList": { + "message": "यहां एक सूची है !!!!" + }, + "hide": { + "message": "छुपाएं" + }, + "hideToken": { + "message": "टोकन छिपाएं" + }, + "hideTokenPrompt": { + "message": "टोकन छिपाएंn?" + }, + "howToDeposit": { + "message": "आप ईथर जमा कैसे करना चाहेंगे?" + }, + "holdEther": { + "message": "यह आपको आकाश और टोकन रखने की अनुमति देता है, और विकेंद्रीकृत अनुप्रयोगों के लिए आपके पुल के रूप में कार्य करता है।" + }, + "import": { + "message": "आयात", + "description": "एक चयनित फ़ाइल से एक खाता आयात करने के लिए बटन " + }, + "importAccount": { + "message": "खाता आयात" + }, + "importAccountMsg": { + "message": "आयात किए गए खाते आपके मूल रूप से बनाए गए मेटामास्क अकाउंट सीडफ्रेज से संबद्ध नहीं होंगे। आयात किए गए खातों के बारे में और जानें" + }, + "importAnAccount": { + "message": "खाता आयात करैॉ" + }, + "importDen": { + "message": "मौजूदा डेन आयात करें - DEN" + }, + "imported": { + "message": "आयातित", + "description": "यह स्थिति दिखाती है कि कोई खाता पूरी तरह से कीरिंग में लोड हो चुका है" + }, + "infoHelp": { + "message": "जानकारी और सहायता" + }, + "insufficientFunds": { + "message": "अपर्याप्त धन" + }, + "insufficientTokens": { + "message": "अपर्याप्त टोकन।" + }, + "invalidAddress": { + "message": "अमान्य पता" + }, + "invalidAddressRecipient": { + "message": "प्राप्तकर्ता का पता अमान्य है" + }, + "invalidGasParams": { + "message": "अमान्य गैस पैरामीटर" + }, + "invalidInput": { + "message": "अमान्य इनपुट।" + }, + "invalidRequest": { + "message": "अमान्य अनुरोध" + }, + "invalidRPC": { + "message": "अमान्य RPC कै URI" + }, + "jsonFail": { + "message": "कुछ गलत हो गया। कृपया सुनिश्चित करें कि आपकी JSON फ़ाइल ठीक से फ़ॉर्मेट की गई है।" + }, + "jsonFile": { + "message": "JSON फ़ाइल", + "description": "एक खाता आयात करने के लिए प्रारूप" + }, + "kovan": { + "message": "कोवान टेस्ट नेटवर्क" + }, + "knowledgeDataBase": { + "message": "हमारे नॉलेज बेस पर जाएं" + }, + "lessThanMax": { + "message": "$1 से कम या बराबर होना चाहिए।", + "description": "हेक्स इनपुट के लिए दशमलव इनपुट के रूप में सहायक" + }, + "likeToAddTokens": { + "message": "क्या आप इन टोकनों को जोड़ना चाहते हैं?" + }, + "limit": { + "message": "सीमा" + }, + "loading": { + "message": "लोड हो रहा है ....." + }, + "loadingTokens": { + "message": "टोकन लोड हो रहा है ....." + }, + "localhost": { + "message": "स्थानीयहोस्ट 8545" + }, + "login": { + "message": "लॉग इन करें" + }, + "logout": { + "message": "लॉग आउट करें" + }, + "loose": { + "message": "ढीला" + }, + "loweCaseWords": { + "message": "बीज शब्द में केवल लोअरकेस वर्ण होते हैं" + }, + "mainnet": { + "message": "मुख्य ईथरम नेटवर्क" + }, + "message": { + "message": "संदेश" + }, + "metamaskDescription": { + "message": "मेटामास्क एथर्मम के लिए एक सुरक्षित पहचान वॉल्ट है।" + }, + "min": { + "message": "न्यूनतम" + }, + "myAccounts": { + "message": "मेरे खाते" + }, + "mustSelectOne": { + "message": "कम से कम 1 टोकन का चयन करना आवश्यक है।" + }, + "needEtherInWallet": { + "message": "मेटामास्क का उपयोग करने वाले विकेन्द्रीकृत अनुप्रयोगों के साथ बातचीत करने के लिए, आपको अपने वॉलेट में ईथर की आवश्यकता होगी।" + }, + "needImportFile": { + "message": "आयात करने के लिए आपको एक फ़ाइल का चयन करना होगा।", + "description": "प्रयोक्ता महत्वपूर्ण खाता है और उसे जारी रखने के लिए एक फ़ाइल जोड़ने की आवश्यकता है" + }, + "needImportPassword": { + "message": "चयनित फ़ाइल के लिए आपको एक पासवर्ड दर्ज करना होगा।", + "description": "पासवर्ड और फाइल को एक खाते आयात करने के लिए आवश्यक है" + }, + "negativeETH": { + "message": "ईटीएच की नकारात्मक मात्रा नहीं भेज सकते हैं।." + }, + "networks": { + "message": "नेटवर्क" + }, + "newAccount": { + "message": "नया खाता" + }, + "newAccountNumberName": { + "message": "नया खाता $1", + "description": "खाते का खाता बनाने पर अगले खाते का डिफ़ॉल्ट नाम" + }, + "newContract": { + "message": "नया अनुबंध" + }, + "newPassword": { + "message": "नया पासवर्ड (न्यूनतम 8 वर्ण)" + }, + "newRecipient": { + "message": "नया प्राप्तकर्ता" + }, + "newRPC": { + "message": "नया RPC URL" + }, + "next": { + "message": "अगला" + }, + "noAddressForName": { + "message": "इस नाम के लिए कोई पता सेट नहीं किया गया है।" + }, + "noDeposits": { + "message": "कोई जमा प्राप्त नहीं हुई" + }, + "noTransactionHistory": { + "message": "कोई लेनदेन इतिहास नहीं ..." + }, + "noTransactions": { + "message": "कोई लेन-देन नहीं" + }, + "notStarted": { + "message": "प्रारंभ नहीं किया गया" + }, + "oldUI": { + "message": "पुराना UI" + }, + "oldUIMessage": { + "message": "आप पुराने UI पर वापस आ गए हैं। आप ऊपर दाईं ओर ड्रॉपडाउन मेनू में विकल्प के माध्यम से नए UI पर वापस स्विच कर सकते हैं।" + }, + "or": { + "message": "या", + "description": "एक नया खाता बनाने या आयात करने के बीच की पसंद" + }, + "passwordCorrect": { + "message": "कृपया सुनिश्चित करें कि आपका पासवर्ड सही है।" + }, + "passwordMismatch": { + "message": "पासवर्ड मेल नहीं खाते", + "description": "पासवर्ड निर्माण प्रक्रिया में, दो नए पासवर्ड फ़ील्ड मेल नहीं खाते" + }, + "passwordShort": { + "message": "पासवर्ड पर्याप्त लंबा नहीं", + "description": "पासवर्ड बनाने की प्रक्रिया में, पासवर्ड सुरक्षित होने के लिए पर्याप्त नहीं है" + }, + "pastePrivateKey": { + "message": "यहां अपनी निजी कुंजी स्ट्रिंग चिपकाएं:", + "description": "किसी निजी कुंजी से किसी खाते को आयात करने के लिए" + }, + "pasteSeed": { + "message": "यहां अपने बीज वाक्यांश पेस्ट करें!" + }, + "personalAddressDetected": { + "message": "व्यक्तिगत पता मिला। टोकन अनुबंध का पता इनपुट।" + }, + "pleaseReviewTransaction": { + "message": "कृपया अपने लेनदेन की समीक्षा करें।" + }, + "privacyMsg": { + "message": "गोपनीयता नीति" + }, + "privateKey": { + "message": "निजी कुंजी", + "description": "खाता आयात करने के लिए उपयोग करने के लिए इस प्रकार की फ़ाइल का चयन करें" + }, + "privateKeyWarning": { + "message": "चेतावनी: कभी भी इस कुंजी का खुलासा न करें। आपकी निजी कुंजी वाले कोई भी आपके खाते में रखी किसी भी संपत्ति को चुरा सकता है।" + }, + "privateNetwork": { + "message": "निजी नेटवर्क" + }, + "qrCode": { + "message": "QR कोड दिखाएं" + }, + "readdToken": { + "message": "आप अपने खाता विकल्प मेनू में .टोकन जोड़ें. पर जाकर भविष्य में इस टोकन को वापस जोड़ सकते हैं।" + }, + "readMore": { + "message": "यहां और पढ़ें।" + }, + "readMore2": { + "message": "हां - और पढ़ें।" + }, + "receive": { + "message": "प्राप्त करें" + }, + "recipientAddress": { + "message": "प्राप्तकर्ता पता" + }, + "refundAddress": { + "message": "आपका रिफंड पता" + }, + "rejected": { + "message": "अस्वीकृत" + }, + "resetAccount": { + "message": "खाता रीसेट करें" + }, + "restoreFromSeed": { + "message": "बीज वाक्यांश से पुनर्स्थापित करें" + }, + "required": { + "message": "आवश्यक" + }, + "retryWithMoreGas": { + "message": "यहां एक उच्च गैस कीमत के साथ पुन: प्रयास करें" + }, + "revealSeedWords": { + "message": "बीज शब्द प्रकट करें" + }, + "revealSeedWordsWarning": { + "message": "किसी सार्वजनिक स्थान पर अपने बीज के शब्द ठीक नहीं करें! ये शब्द आपके सभी खातों को चोरी करने के लिए उपयोग किए जा सकते हैं।" + }, + "revert": { + "message": "वापस" + }, + "rinkeby": { + "message": "रिचीव टेस्ट नेटवर्क" + }, + "ropsten": { + "message": "रॉप्स्टेन टेस्ट नेटवर्क" + }, + "sampleAccountName": { + "message": "उदाहरण के लिए मेरा नया खाता", + "description": "उपयोगकर्ता को अपने खाते में मानव-पठनीय नाम जोड़ने की अवधारणा को समझें." + }, + "save": { + "message": "सहेजें" + }, + "saveAsFile": { + "message": "फ़ाइल के रूप में सहेजें", + "description": "खाता निर्यात प्रक्रिया" + }, + "saveSeedAsFile": { + "message": "सेड वर्ड्स - फाईल्स सेव करें" + }, + "search": { + "message": "खोज करें" + }, + "secretPhrase": { + "message": "अपनी गुप्त बारह शब्द वाक्यांश यहाँ अपनी तिजोरी को पुनर्स्थापित करने के लिए दर्ज करें।" + }, + "seedPhraseReq": { + "message": "बीज वाक्यांश 12 शब्द लंबा हैं" + }, + "select": { + "message": "चुनें" + }, + "selectCurrency": { + "message": "मुद्रा चुनें" + }, + "selectService": { + "message": "सेवा चुनें" + }, + "selectType": { + "message": "प्रकार चुनें" + }, + "send": { + "message": "भेजें" + }, + "sendETH": { + "message": "भेजें ETH" + }, + "sendTokens": { + "message": "भेजें टोकन" + }, + "sendTokensAnywhere": { + "message": "इटोरम खाते वाले किसी को भी टोकन भेजें" + }, + "settings": { + "message": "सेटिंग्स" + }, + "shapeshiftBuy": { + "message": "शेपेशिस्ट के साथ खरीदें" + }, + "showPrivateKeys": { + "message": "निजी कुंजी दिखाएँ" + }, + "showQRCode": { + "message": "QR कोड दिखाएं" + }, + "sign": { + "message": "हस्ताक्षर" + }, + "signMessage": { + "message": "हस्ताक्षर संदेश" + }, + "signNotice": { + "message": "इस संदेश पर हस्ताक्षर करने से \n साइड इफेक्ट हो सकते हैं। \n केवल अपने पूरे खाते के साथ पूरी तरह से भरोसेमंद \n साइटों से संदेश पर हस्ताक्षर करें। \n यह खतरनाक विधि भविष्य के संस्करण में निकाल दी जाएगी।" + }, + "sigRequest": { + "message": "हस्ताक्षर अनुरोध" + }, + "sigRequested": { + "message": "हस्ताक्षर अनुरोधित" + }, + "spaceBetween": { + "message": "केवल शब्दों के बीच एक स्थान हो सकता है" + }, + "status": { + "message": "स्थिति" + }, + "stateLogs": { + "message": "स्थिति संदेश" + }, + "stateLogsDescription": { + "message": "स्थिति संदेश में आपका सार्वजनिक खाता, पतों और भेजे गए लेनदेन, होते हैं।" + }, + "submit": { + "message": "सबमिट करें" + }, + "supportCenter": { + "message": "हमारे सहायता केंद्र पर जाएं" + }, + "symbolBetweenZeroTen": { + "message": "प्रतीक 0 और 10 अक्षरों के बीच होना चाहिए" + }, + "takesTooLong": { + "message": "बहुत समय ले रहा है?" + }, + "terms": { + "message": "उपयोग की शर्तें" + }, + "testFaucet": { + "message": "टेस्ट नलि" + }, + "to": { + "message": "के लिए" + }, + "toETHviaShapeShift": { + "message": "शैपशैफ्ट द्वारा $1 से ETH में जमा", + "description": "संदेश की शुरुआत में जमा प्रकार भर जाएगा" + }, + "tokenAddress": { + "message": "टोकन पता" + }, + "tokenAlreadyAdded": { + "message": "टोकन पहले ही जोड़ा जा चुका है।" + }, + "tokenBalance": { + "message": "आपका टोकन बैलेंस है:" + }, + "tokenSelection": { + "message": "टोकन के लिए खोजें या हमारी लोकप्रिय टॉकेन्स की सूची से चुनें।" + }, + "tokenSymbol": { + "message": "टोकन प्रतीक" + }, + "tokenWarning1": { + "message": "अपने मेटामास्क अकाउंट से खरीदे गए टोकनों का नज़र रखें। अगर आप किसी अलग खाते का उपयोग कर टोकन खरीदे हैं, तो ये टोकन यहां दिखाई नहीं देंगे।" + }, + "total": { + "message": "कुल" + }, + "transactions": { + "message": "लेनदेन" + }, + "transactionMemo": { + "message": "लेनदेन मेमो (वैकल्पिक)" + }, + "transactionNumber": { + "message": "लेनदेन संख्या" + }, + "transfers": { + "message": "स्थानांतरण" + }, + "troubleTokenBalances": { + "message": "मुसीबत... आपके टोकन शेष राशि को लोड करने में हमें परेशानी हुई थी। आप उन्हें देख सकते हैं", + "description": "टोकन शेष देखने के लिए एक लिंक ... (यहां)" + }, + "twelveWords": { + "message": "ये 12 शब्द आपके मेटामास्क खातों को पुनर्स्थापित करने का एकमात्र तरीका है। \n उन्हें कहीं सुरक्षित और गुप्त रूप से सहेजें।" + }, + "typePassword": { + "message": "अपना पासवर्ड टाइप करें" + }, + "uiWelcome": { + "message": "नया यूआई (बीटा) में आपका स्वागत है" + }, + "uiWelcomeMessage": { + "message": "आप अब नए मेटामास्क UI का उपयोग कर रहे हैं। चारों ओर एक नज़र डालें, टोकन भेजने की तरह नई सुविधाएं देखें, और हमें बताएं कि आपके पास कोई समस्या है।" + }, + "unavailable": { + "message": "अनुपलब्ध" + }, + "unknown": { + "message": "अज्ञात नेटवर्क" + }, + "unknownNetwork": { + "message": "अज्ञात निजी नेटवर्क" + }, + "unknownNetworkId": { + "message": "अज्ञात नेटवर्क आईडी.य़" + }, + "uriErrorMsg": { + "message": "URI-यूआरआई को उपयुक्त HTTP / HTTPS उपसर्ग की आवश्यकता होती है।" + }, + "usaOnly": { + "message": "केवल यूएसए - USA", + "description": "इस एक्सचेंज का उपयोग करना संयुक्त राज्य अमेरिका के अंदर ही सीमित है" + }, + "usedByClients": { + "message": "विभिन्न क्लाइंट्स द्वारा उपयोग किया जाता है" + }, + "useOldUI": { + "message": "पुराने UI का उपयोग करें" + }, + "validFileImport": { + "message": "आयात करने के लिए आपको एक वैध फ़ाइल चुननी होगी।" + }, + "vaultCreated": { + "message": "वॉल्ट बनाया गया" + }, + "viewAccount": { + "message": "खाता देखें" + }, + "visitWebSite": { + "message": "हमारी वेब साइट पर जाएं" + }, + "warning": { + "message": "चेतावनी" + }, + "welcomeBeta": { + "message": "मेटामास्क बीटा में आपका स्वागत है" + }, + "whatsThis": { + "message": "यह क्या है?" + }, + "yourSigRequested": { + "message": "आपका हस्ताक्षर अनुरोध किया जा रहा है" + }, + "youSign": { + "message": "आप हस्ताक्षर कर रहे हैं" + } +} diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json new file mode 100644 index 000000000..f54ef98ca --- /dev/null +++ b/app/_locales/it/messages.json @@ -0,0 +1,819 @@ +{ + "accept": { + "message": "Accetta" + }, + "account": { + "message": "Account" + }, + "accountDetails": { + "message": "Dettagli Account" + }, + "accountName": { + "message": "Nome Account" + }, + "address": { + "message": "Indirizzo" + }, + "addCustomToken": { + "message": "Aggiungi un token personalizzato" + }, + "addToken": { + "message": "Aggiungi Token" + }, + "addTokens": { + "message": "Aggiungi più token" + }, + "amount": { + "message": "Importo" + }, + "amountPlusGas": { + "message": "Importo + Gas" + }, + "appDescription": { + "message": "Ethereum Browser Extension", + "description": "La descrizione dell'applicazione" + }, + "appName": { + "message": "MetaMask", + "description": "Il nome dell'applicazione" + }, + "attemptingConnect": { + "message": "Tentativo di connessione alla blockchain." + }, + "attributions": { + "message": "Attribuzioni" + }, + "available": { + "message": "Disponibile" + }, + "back": { + "message": "Indietro" + }, + "balance": { + "message": "Bilancio:" + }, + "balances": { + "message": "I tuoi bilanci" + }, + "balanceIsInsufficientGas": { + "message": "Bilancio insufficiente per il gas totale corrente" + }, + "beta": { + "message": "BETA" + }, + "betweenMinAndMax": { + "message": "deve essere maggiore o uguale a $1 e minore o uguale a $2.", + "description": "aiuto per inserire un input esadecimale come decimale" + }, + "blockiesIdenticon": { + "message": "Usa le icone Blockie" + }, + "borrowDharma": { + "message": "Prendi in presisito con Dharma (Beta)" + }, + "builtInCalifornia": { + "message": "MetaMask è progettato e costruito in California." + }, + "buy": { + "message": "Compra" + }, + "buyCoinbase": { + "message": "Compra su Coinbase" + }, + "buyCoinbaseExplainer": { + "message": "Coinbase è il servizio più popolare al mondo per comprare e vendere bitcoin, ethereum e litecoin." + }, + "cancel": { + "message": "Cancella" + }, + "classicInterface": { + "message": "Usa l'interfaccia classica" + }, + "clickCopy": { + "message": "Clicca per Copiare" + }, + "confirm": { + "message": "Conferma" + }, + "confirmContract": { + "message": "Conferma Contratto" + }, + "confirmPassword": { + "message": "Conferma Password" + }, + "confirmTransaction": { + "message": "Conferma Transazione" + }, + "continue": { + "message": "Continua" + }, + "continueToCoinbase": { + "message": "Continua su Coinbase" + }, + "contractDeployment": { + "message": "Distribuzione Contratto" + }, + "conversionProgress": { + "message": "Conversione in corso" + }, + "copiedButton": { + "message": "Copiato" + }, + "copiedClipboard": { + "message": "Copiato negli Appunti" + }, + "copiedExclamation": { + "message": "Copiato!" + }, + "copiedSafe": { + "message": "Le ho copiate in un posto sicuro" + }, + "copy": { + "message": "Copia" + }, + "copyToClipboard": { + "message": "Copia negli appunti" + }, + "copyButton": { + "message": " Copia " + }, + "copyPrivateKey": { + "message": "Questa è la tua chiave privata (clicca per copiare)" + }, + "create": { + "message": "Crea" + }, + "createAccount": { + "message": "Crea Account" + }, + "createDen": { + "message": "Crea" + }, + "crypto": { + "message": "Crypto", + "description": "Tipo di exchange (cryptomonete)" + }, + "currentConversion": { + "message": "Cambio Corrente" + }, + "currentNetwork": { + "message": "Rete Corrente" + }, + "customGas": { + "message": "Personalizza Gas" + }, + "customize": { + "message": "Personalizza" + }, + "customRPC": { + "message": "RPC Personalizzata" + }, + "decimalsMustZerotoTen": { + "message": "Il numero di decimali deve essere almeno 0, e non oltre 36." + }, + "decimal": { + "message": "Precisione Decimali" + }, + "defaultNetwork": { + "message": "La rete predefinita per transazioni in Ether è la Rete Ethereum Principale." + }, + "denExplainer": { + "message": "Il DEN è il tuo archivio crittato con password dentro Metamask." + }, + "deposit": { + "message": "Deposita" + }, + "depositBTC": { + "message": "Deposita i tuoi BTC all'indirizzo sotto:" + }, + "depositCoin": { + "message": "Deposita $1 all'indirizzo sotto", + "description": "Dice all'utente quale moneta ha selezionato per depositare con Shapeshift" + }, + "depositEth": { + "message": "Deposita Eth" + }, + "depositEther": { + "message": "Deposita Ether" + }, + "depositFiat": { + "message": "Deposita con moneta Fiat" + }, + "depositFromAccount": { + "message": "Deposita da un altro account" + }, + "depositShapeShift": { + "message": "Deposita con ShapeShift" + }, + "depositShapeShiftExplainer": { + "message": "Se possiedi altre criptomonete, puoi scambiare e depositare Ether direttamente nel tuo portafoglio MetaMask. Nessun account richiesto." + }, + "details": { + "message": "Dettagli" + }, + "directDeposit": { + "message": "Deposito Diretto" + }, + "directDepositEther": { + "message": "Deposita Direttamente Ether" + }, + "directDepositEtherExplainer": { + "message": "Se possiedi già degli Ether, questa è la via più veloce per aggiungere Ether al tuo portafoglio con un deposito diretto." + }, + "done": { + "message": "Finito" + }, + "downloadStatelogs": { + "message": "Scarica i log di Stato" + }, + "edit": { + "message": "Modifica" + }, + "editAccountName": { + "message": "Modifica Nome Account" + }, + "emailUs": { + "message": "Mandaci una mail!" + }, + "encryptNewDen": { + "message": "Cripta il tuo nuovo DEN" + }, + "enterPassword": { + "message": "Inserisci password" + }, + "enterPasswordConfirm": { + "message": "Inserisci la tua password per confermare" + }, + "etherscanView": { + "message": "Vedi account su Etherscan" + }, + "exchangeRate": { + "message": "Tasso di cambio" + }, + "exportPrivateKey": { + "message": "Esporta Chiave Privata" + }, + "exportPrivateKeyWarning": { + "message": "Esporta chiave privata a tuo rischio." + }, + "failed": { + "message": "Fallito" + }, + "fiat": { + "message": "FIAT", + "description": "Tipo di scambio" + }, + "fileImportFail": { + "message": "Importazione file non funziona? Clicca qui!", + "description": "Aiuta gli utenti a importare il loro account da un file JSON" + }, + "followTwitter": { + "message": "Seguici su Twitter" + }, + "from": { + "message": "Da" + }, + "fromToSame": { + "message": "Gli indirizzi Da e A non possono essere uguali" + }, + "fromShapeShift": { + "message": "Da ShapeShift" + }, + "gas": { + "message": "Gas", + "description": "Piccola indicazione del costo del gas" + }, + "gasFee": { + "message": "Costo Gas" + }, + "gasLimit": { + "message": "Gas Limite" + }, + "gasLimitCalculation": { + "message": "Calcoliamo il gas limite suggerito in base al successo delle transazioni in rete." + }, + "gasLimitRequired": { + "message": "Gas Limite Richiesto" + }, + "gasLimitTooLow": { + "message": "Il Gas Limite deve essere almeno 21000" + }, + "generatingSeed": { + "message": "Generando la frase seed..." + }, + "gasPrice": { + "message": "Prezzo del Gas (GWEI)" + }, + "gasPriceCalculation": { + "message": "Calcoliamo il gas limite suggerito in base al successo delle transazioni in rete." + }, + "gasPriceRequired": { + "message": "Prezzo Gas Richiesto" + }, + "getEther": { + "message": "Ottieni Ether" + }, + "getEtherFromFaucet": { + "message": "Ottieni Get Ether da un faucet per $1", + "description": "Visualizza il nome della rete per il faucet Ether" + }, + "greaterThanMin": { + "message": "deve essere maggiore o uguale a $1.", + "description": "aiuto per inserire un input esadecimale come decimale" + }, + "here": { + "message": "qui", + "description": "per intendere -clicca qui- per maggiori informazioni (va con troubleTokenBalances)" + }, + "hereList": { + "message": "Questa è una lista!!!!" + }, + "hide": { + "message": "Nascondi" + }, + "hideToken": { + "message": "Nascondi Token" + }, + "hideTokenPrompt": { + "message": "Nascondi Token?" + }, + "howToDeposit": { + "message": "Come vuoi depositare Ether?" + }, + "holdEther": { + "message": "Ti permette di tenere ether & token, e serve da ponte per le applicazioni decentralizzate." + }, + "import": { + "message": "Importa", + "description": "Tasto per importare un account da un file selezionato" + }, + "importAccount": { + "message": "Importa Account" + }, + "importAccountMsg": { + "message":" Gli account importati non saranno associati alla frase seed originariamente creata con MetaMask. Impara di più sugli account importati " + }, + "importAnAccount": { + "message": "Importa un account" + }, + "importDen": { + "message": "Importa un DEN Esistente" + }, + "imported": { + "message": "Importato", + "description": "stato che conferma che un account è stato totalmente caricato nel portachiavi" + }, + "infoHelp": { + "message": "Informazioni & Aiuto" + }, + "insufficientFunds": { + "message": "Fondi non sufficienti." + }, + "insufficientTokens": { + "message": "Token non sufficienti." + }, + "invalidAddress": { + "message": "Indirizzo non valido" + }, + "invalidAddressRecipient": { + "message": "Indirizzo destinatario invalido" + }, + "invalidGasParams": { + "message": "Parametri del Gas non validi" + }, + "invalidInput": { + "message": "Input non valido." + }, + "invalidRequest": { + "message": "Richiesta non Valida" + }, + "invalidRPC": { + "message": "URI RPC invalido" + }, + "jsonFail": { + "message": "Qualcosa è andato storto. Assicurati che il file JSON sia formattato correttamente." + }, + "jsonFile": { + "message": "File JSON", + "description": "formato per importare un account" + }, + "kovan": { + "message": "Rete di test Kovan" + }, + "knowledgeDataBase": { + "message": "Visita la nostra Knowledge Base" + }, + "lessThanMax": { + "message": "deve essere minore o uguale a $1.", + "description": "aiuto per inserire un input esadecimale come decimale" + }, + "likeToAddTokens": { + "message": "Vorresti aggiungere questi token?" + }, + "limit": { + "message": "Limite" + }, + "loading": { + "message": "Caricamento..." + }, + "loadingTokens": { + "message": "Caricamento Tokens..." + }, + "localhost": { + "message": "Localhost 8545" + }, + "login": { + "message": "Connetti" + }, + "logout": { + "message": "Disconnetti" + }, + "loose": { + "message": "Libero" + }, + "loweCaseWords": { + "message": "le frasi seed hanno solo lettere minuscole" + }, + "mainnet": { + "message": "Rete Ethereum Principale" + }, + "message": { + "message": "Messaggio" + }, + "metamaskDescription": { + "message": "MetaMask è una cassaforte sicura per identità su Ethereum." + }, + "min": { + "message": "Minimo" + }, + "myAccounts": { + "message": "Account Miei" + }, + "mustSelectOne": { + "message": "Devi selezionare almeno un token." + }, + "needEtherInWallet": { + "message": "Per interagire con applicazioni decentralizzate con MetaMask, devi possedere Ether nel tuo portafoglio." + }, + "needImportFile": { + "message": "Devi selezionare un file da importare.", + "description": "L'utente sta importando un account e deve aggiungere un file per continuare" + }, + "needImportPassword": { + "message": "Dei inserire una password per il file selezionato.", + "description": "Password e file necessari per importare un account" + }, + "negativeETH": { + "message": "Non puoi inviare una quantità di ETH negativa." + }, + "networks": { + "message": "Reti" + }, + "newAccount": { + "message": "Nuovo Account" + }, + "newAccountNumberName": { + "message": "Account $1", + "description": "Nome predefinito per il prossimo account da creare nella schermata di creazione account" + }, + "newContract": { + "message": "Nuovo Contratto" + }, + "newPassword": { + "message": "Nuova Password (minimo 8 caratteri)" + }, + "newRecipient": { + "message": "Nuovo Destinatario" + }, + "newRPC": { + "message": "Nuovo URL RPC" + }, + "next": { + "message": "Prossimo" + }, + "noAddressForName": { + "message": "Nessun indirizzo è stato impostato per questo nome." + }, + "noDeposits": { + "message": "Nessun deposito ricevuto" + }, + "noTransactionHistory": { + "message": "Nessuna cronologia delle transazioni." + }, + "noTransactions": { + "message": "Nessuna Transazione" + }, + "notStarted": { + "message": "Non Iniziato" + }, + "oldUI": { + "message": "Vecchia interfaccia" + }, + "oldUIMessage": { + "message": "Sei ritornato alla vecchia interfaccia. Puoi ritornare alla nuova interfaccia tramite l'opzione nel menu a discesa in alto a destra." + }, + "or": { + "message": "o", + "description": "scelta tra creare o importare un nuovo account" + }, + "passwordCorrect": { + "message": "Assicurati che la password sia corretta." + }, + "passwordMismatch": { + "message": "le password non corrispondono", + "description": "nella creazione della password, le due password all'interno dei campi non corrispondono" + }, + "passwordShort": { + "message": "password non sufficientemente lunga", + "description": "nella creazione della password, la password non è lunga abbastanza" + }, + "pastePrivateKey": { + "message": "Incolla la tua chiave privata qui:", + "description": "Per importare un account da una chiave privata" + }, + "pasteSeed": { + "message": "Incolla la tua frase seed qui!" + }, + "personalAddressDetected": { + "message": "Rilevato indirizzo personale. Inserisci l'indirizzo del contratto del token." + }, + "pleaseReviewTransaction": { + "message": "Ricontrolla la tua transazione." + }, + "privacyMsg": { + "message": "Politica sulla Privacy" + }, + "privateKey": { + "message": "Chiave Privata", + "description": "seleziona questo tipo di file per importare un account" + }, + "privateKeyWarning": { + "message": "Attenzione: non dire a nessuno questa chiave! Chiunque con la tua chiave privata può rubare qualsiasi moneta contenuta nel tuo account." + }, + "privateNetwork": { + "message": "Rete Privata" + }, + "qrCode": { + "message": "Mostra Codice QR" + }, + "readdToken": { + "message": "Puoi aggiungere nuovamente questo token in futuro andando in “Aggiungi token” nel menu delle opzioni del tuo account." + }, + "readMore": { + "message": "Leggi di più qui." + }, + "readMore2": { + "message": "Leggi di più." + }, + "receive": { + "message": "Ricevi" + }, + "recipientAddress": { + "message": "Indirizzo Destinatario" + }, + "refundAddress": { + "message": "Indirizzo di Rimborso" + }, + "rejected": { + "message": "Respinta" + }, + "resetAccount": { + "message": "Resetta Account" + }, + "restoreFromSeed": { + "message": "Ripristina da una frase seed" + }, + "required": { + "message": "Richiesto" + }, + "retryWithMoreGas": { + "message": "Riprova con un prezzo del Gas maggiore qui" + }, + "revealSeedWords": { + "message": "Rivela Frase Seed" + }, + "revealSeedWordsWarning": { + "message": "Non ripristinare la tua frase seed in pubblico!. Queste parole possono essere usate per rubare il tuo account." + }, + "revert": { + "message": "Annulla" + }, + "rinkeby": { + "message": "Rete di test Rinkeby" + }, + "ropsten": { + "message": "Rete di test Ropsten" + }, + "sampleAccountName": { + "message": "Es: Il mio nuovo account", + "description": "Aiuta l'utente a capire il concetto di aggiungere un nome leggibile al loro account" + }, + "save": { + "message": "Salva" + }, + "saveAsFile": { + "message": "Salva come File", + "description": "Processo per esportare un account" + }, + "saveSeedAsFile": { + "message": "Salva la Frase Seed come File" + }, + "search": { + "message": "Cerca" + }, + "secretPhrase": { + "message": "Inserisci la tua frase segreta di dodici parole per ripristinare la cassaforte." + }, + "seedPhraseReq": { + "message": "le frasi seed sono lunghe 12 parole" + }, + "select": { + "message": "Seleziona" + }, + "selectCurrency": { + "message": "Seleziona Moneta" + }, + "selectService": { + "message": "Seleziona Servizio" + }, + "selectType": { + "message": "Seleziona Tipo" + }, + "send": { + "message": "Invia" + }, + "sendETH": { + "message": "Invia ETH" + }, + "sendTokens": { + "message": "Invia Tokens" + }, + "sendTokensAnywhere": { + "message": "Invia Tokens a chiunque abbia un account Ethereum" + }, + "settings": { + "message": "Impostazioni" + }, + "shapeshiftBuy": { + "message": "Compra con Shapeshift" + }, + "showPrivateKeys": { + "message": "Mostra Chiave Privata" + }, + "showQRCode": { + "message": "Mostra Codie QR" + }, + "sign": { + "message": "Firma" + }, + "signMessage": { + "message": "Firma Messaggio" + }, + "signNotice": { + "message": "Firmare questo messaggio può avere effetti collaterali pericolosi. \nFirma messaggi da siti di cui ti fidi totalmente. \nQuesto metodo pericoloso sarà rimosso in versioni future." + }, + "sigRequest": { + "message": "Firma Richiesta" + }, + "sigRequested": { + "message": "Richiesta Firma" + }, + "spaceBetween": { + "message": "ci può essere solo uno spazio tra le parole" + }, + "status": { + "message": "Stato" + }, + "stateLogs": { + "message": "Log di Stato" + }, + "stateLogsDescription": { + "message": "I log di stato contengono i tuoi indirizzi pubblici e le transazioni effettuate." + }, + "submit": { + "message": "Invia" + }, + "supportCenter": { + "message": "Visita il nostro Centro di Supporto" + }, + "symbolBetweenZeroTen": { + "message": "Il simbolo deve essere lungo tra 0 e 10 caratteri." + }, + "takesTooLong": { + "message": "Ci sta mettendo troppo?" + }, + "terms": { + "message": "Termini di Uso" + }, + "testFaucet": { + "message": "Prova Faucet" + }, + "to": { + "message": "A" + }, + "toETHviaShapeShift": { + "message": "$1 a ETH via ShapeShift", + "description": "il sistema riempirà il tipo di deposito all'inizio del messaggio" + }, + "tokenAddress": { + "message": "Indirizzo Token" + }, + "tokenAlreadyAdded": { + "message": "Il token è già stato aggiunto." + }, + "tokenBalance": { + "message": "Bilancio Token:" + }, + "tokenSelection": { + "message": "Cerca un token o seleziona dalla lista di token più popolari." + }, + "tokenSymbol": { + "message": "Simbolo Token" + }, + "tokenWarning1": { + "message": "Tieni traccia dei token che hai acquistato con il tuo account MetaMask. Se hai acquistato token con un account diverso, quei token non appariranno qui." + }, + "total": { + "message": "Totale" + }, + "transactions": { + "message": "transazioni" + }, + "transactionMemo": { + "message": "Promemoria Transazione (opzionale)" + }, + "transactionNumber": { + "message": "Numero Transazione" + }, + "transfers": { + "message": "Trasferimenti" + }, + "troubleTokenBalances": { + "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" + }, + "twelveWords": { + "message": "Queste 12 parole sono l'unico modo per ripristinare i tuoi account MetaMask. \nSalvale in un posto sicuro e segreto." + }, + "typePassword": { + "message": "Inserisci Password" + }, + "uiWelcome": { + "message": "Benvenuto alla nuova interfaccia (Beta)" + }, + "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." + }, + "unavailable": { + "message": "Non Disponibile" + }, + "unknown": { + "message": "Sconosciuto" + }, + "unknownNetwork": { + "message": "Rete Privata Sconosciuta" + }, + "unknownNetworkId": { + "message": "ID rete sconosciuto" + }, + "uriErrorMsg": { + "message": "Gli URI richiedono un prefisso HTTP/HTTPS." + }, + "usaOnly": { + "message": "Solo USA", + "description": "Usare questo sito di scambio è possibile solo per persone residenti in USA." + }, + "usedByClients": { + "message": "Usato da una varietà di clients diversi" + }, + "useOldUI": { + "message": "Use la vecchia UI" + }, + "validFileImport": { + "message": "Devi selezionare un file valido da importare." + }, + "vaultCreated": { + "message": "Cassaforte Creata" + }, + "viewAccount": { + "message": "Vedi Account" + }, + "visitWebSite": { + "message": "Visita il nostro sito web" + }, + "warning": { + "message": "Attenzione" + }, + "welcomeBeta": { + "message": "Benvenuto nella Beta di MetaMask" + }, + "whatsThis": { + "message": "Cos'è questo?" + }, + "yourSigRequested": { + "message": "E' richiesta la tua firma" + }, + "youSign": { + "message": "Ti stai connettendo" + } +}
\ No newline at end of file diff --git a/app/_locales/nl/messages.json b/app/_locales/nl/messages.json new file mode 100644 index 000000000..aacb81fee --- /dev/null +++ b/app/_locales/nl/messages.json @@ -0,0 +1,819 @@ +{ + "accept": { + "message": "Aanvaarden" + }, + "account": { + "message": "Account" + }, + "accountDetails": { + "message": "Accountgegevens" + }, + "accountName": { + "message": "Accountnaam" + }, + "address": { + "message": "Adres" + }, + "addCustomToken": { + "message": "Aangepaste token toevoegen" + }, + "addToken": { + "message": "Voeg token toe" + }, + "addTokens": { + "message": "Tokens toevoegen" + }, + "amount": { + "message": "Bedrag" + }, + "amountPlusGas": { + "message": "Bedrag + gas" + }, + "appDescription": { + "message": "Ethereum Browser-extensie", + "description": "De beschrijving van de applicatie" + }, + "appName": { + "message": "MetaMask", + "description": "De naam van de applicatie" + }, + "attemptingConnect": { + "message": "Poging om verbinding te maken met blockchain." + }, + "attributions": { + "message": "Bevoegdheden" + }, + "available": { + "message": "Beschikbaar" + }, + "back": { + "message": "Terug" + }, + "balance": { + "message": "Balans:" + }, + "balances": { + "message": "Je saldo" + }, + "balanceIsInsufficientGas": { + "message": "Onvoldoende saldo voor huidig gastotaal" + }, + "beta": { + "message": "BETA" + }, + "betweenMinAndMax": { + "message": "moet groter zijn dan of gelijk zijn aan $1 en kleiner dan of gelijk aan $2.", + "description": "helper voor het invoeren van hex als decimale invoer" + }, + "blockiesIdenticon": { + "message": "Gebruik Blockies Identicon" + }, + "borrowDharma": { + "message": "Lenen met Dharma (Beta)" + }, + "builtInCalifornia": { + "message": "MetaMask is ontworpen en gebouwd in Californië." + }, + "buy": { + "message": "Kopen" + }, + "buyCoinbase": { + "message": "Koop op Coinbase" + }, + "buyCoinbaseExplainer": { + "message": "Coinbase is 's werelds populairste manier om bitcoin, ethereum en litecoin te kopen en verkopen." + }, + "cancel": { + "message": "Annuleer" + }, + "classicInterface": { + "message": "Gebruik de klassieke interface" + }, + "clickCopy": { + "message": "Klik om te kopiëren" + }, + "confirm": { + "message": "Bevestigen" + }, + "confirmContract": { + "message": "Contract bevestigen" + }, + "confirmPassword": { + "message": "bevestig wachtwoord" + }, + "confirmTransaction": { + "message": "Bevestig transactie" + }, + "continue": { + "message": "Doorgaan met" + }, + "continueToCoinbase": { + "message": "Ga verder naar Coinbase" + }, + "contractDeployment": { + "message": "Contractimplementatie" + }, + "conversionProgress": { + "message": "Bezig met conversie" + }, + "copiedButton": { + "message": "gekopieerde" + }, + "copiedClipboard": { + "message": "Gekopieerd naar het klembord" + }, + "copiedExclamation": { + "message": "Gekopieerde!" + }, + "copiedSafe": { + "message": "Ik heb het ergens veilig gekopieerd" + }, + "copy": { + "message": "Kopiëren" + }, + "copyToClipboard": { + "message": "Kopieer naar klembord" + }, + "copyButton": { + "message": " Kopiëren " + }, + "copyPrivateKey": { + "message": "Dit is uw privésleutel (klik om te kopiëren)" + }, + "create": { + "message": "creëren" + }, + "createAccount": { + "message": "Account aanmaken" + }, + "createDen": { + "message": "creëren" + }, + "crypto": { + "message": "crypto", + "description": "Ruiltype (cryptocurrencies)" + }, + "currentConversion": { + "message": "Huidige conversie" + }, + "currentNetwork": { + "message": "Huidig netwerk" + }, + "customGas": { + "message": "Pas Gas aan" + }, + "customize": { + "message": "Aanpassen" + }, + "customRPC": { + "message": "Aangepaste RPC" + }, + "decimalsMustZerotoTen": { + "message": "Decimalen moeten minimaal 0 en niet meer dan 36 zijn." + }, + "decimal": { + "message": "Decimalen van precisie" + }, + "defaultNetwork": { + "message": "Het standaardnetwerk voor Ether-transacties is Main Net." + }, + "denExplainer": { + "message": "Uw DEN is uw wachtwoord-gecodeerde opslag binnen MetaMask." + }, + "deposit": { + "message": "Storting" + }, + "depositBTC": { + "message": "Stort uw BTC op het onderstaande adres:" + }, + "depositCoin": { + "message": "Stort uw $1 op het onderstaande adres", + "description": "Laat de gebruiker weten welk muntje ze hebben geselecteerd om te deponeren met shapeshift" + }, + "depositEth": { + "message": "Aanbetaling Eth" + }, + "depositEther": { + "message": "Stort Ether" + }, + "depositFiat": { + "message": "Stort met Fiat" + }, + "depositFromAccount": { + "message": "Storten van een ander account" + }, + "depositShapeShift": { + "message": "Stort met ShapeShift" + }, + "depositShapeShiftExplainer": { + "message": "Als u andere cryptocurrencies bezit, kunt u Ether direct in uw MetaMask-portemonnee ruilen en storten. Geen account nodig." + }, + "details": { + "message": "Details" + }, + "directDeposit": { + "message": "Directe storting" + }, + "directDepositEther": { + "message": "Directe Ether storten" + }, + "directDepositEtherExplainer": { + "message": "Als je al wat Ether hebt, de snelste manier om Ether in je nieuwe portemonnee te krijgen door een directe storting." + }, + "done": { + "message": "Gedaan" + }, + "downloadStatelogs": { + "message": "Staatslogboeken downloaden" + }, + "edit": { + "message": "Bewerk" + }, + "editAccountName": { + "message": "Bewerk accountnaam" + }, + "emailUs": { + "message": "Email ons!" + }, + "encryptNewDen": { + "message": "Versleutel je nieuwe DEN" + }, + "enterPassword": { + "message": "Voer wachtwoord in" + }, + "enterPasswordConfirm": { + "message": "Voer uw wachtwoord in om te bevestigen" + }, + "etherscanView": { + "message": "Bekijk account op Etherscan" + }, + "exchangeRate": { + "message": "Wisselkoers" + }, + "exportPrivateKey": { + "message": "Exporteer privésleutel" + }, + "exportPrivateKeyWarning": { + "message": "Exporteer privésleutels op eigen risico." + }, + "failed": { + "message": "mislukt" + }, + "fiat": { + "message": "FIAT", + "description": "Ruiltype" + }, + "fileImportFail": { + "message": "Bestandsimport werkt niet? Klik hier!", + "description": "Helpt de gebruiker om zijn account vanuit een JSON-bestand te importeren" + }, + "followTwitter": { + "message": "Volg ons op Twitter" + }, + "from": { + "message": "Van" + }, + "fromToSame": { + "message": "Van en naar adres kan niet hetzelfde zijn" + }, + "fromShapeShift": { + "message": "Van ShapeShift" + }, + "gas": { + "message": "Gas", + "description": "Korte indicatie van gaskosten" + }, + "gasFee": { + "message": "Gas vergoeding" + }, + "gasLimit": { + "message": "Gaslimiet" + }, + "gasLimitCalculation": { + "message": "We berekenen de voorgestelde gaslimiet op basis van succespercentages van het netwerk." + }, + "gasLimitRequired": { + "message": "Gaslimiet vereist" + }, + "gasLimitTooLow": { + "message": "De gaslimiet moet minstens 21000 zijn" + }, + "generatingSeed": { + "message": "Zaad produceren ..." + }, + "gasPrice": { + "message": "Gasprijs (GWEI)" + }, + "gasPriceCalculation": { + "message": "We berekenen de voorgestelde gasprijzen op basis van succespercentages van het netwerk." + }, + "gasPriceRequired": { + "message": "Gasprijs vereist" + }, + "getEther": { + "message": "Krijg Ether" + }, + "getEtherFromFaucet": { + "message": "Haal Ether uit een kraan voor de $1", + "description": "Geeft de netwerknaam voor Ether-kraan weer" + }, + "greaterThanMin": { + "message": "moet groter zijn dan of gelijk zijn aan $1.", + "description": "helper voor het invoeren van hex als decimale invoer" + }, + "here": { + "message": "hier", + "description": "zoals in -klik hier- voor meer informatie (gaat met troubleTokenBalances)" + }, + "hereList": { + "message": "Hier is een lijst !!!!" + }, + "hide": { + "message": "Verbergen" + }, + "hideToken": { + "message": "Token verbergen" + }, + "hideTokenPrompt": { + "message": "Token verbergen?" + }, + "howToDeposit": { + "message": "Hoe zou je Ether willen deponeren?" + }, + "holdEther": { + "message": "Hiermee kunt u ether & tokens bewaren en dient u als brug naar gedecentraliseerde applicaties." + }, + "import": { + "message": "Importeren", + "description": "Knop om een account uit een geselecteerd bestand te importeren" + }, + "importAccount": { + "message": "Account importeren" + }, + "importAccountMsg": { + "message":" Geïmporteerde accounts worden niet gekoppeld aan de seedphrase van uw oorspronkelijk gemaakte MetaMask-account. Meer informatie over geïmporteerde accounts" + }, + "importAnAccount": { + "message": "Importeer een account" + }, + "importDen": { + "message": "Bestaande DEN importeren" + }, + "imported": { + "message": "geïmporteerde", + "description": "status die aantoont dat een account volledig in de sleutelring is geladen" + }, + "infoHelp": { + "message": "Info en hulp" + }, + "insufficientFunds": { + "message": "Onvoldoende fondsen." + }, + "insufficientTokens": { + "message": "Onvoldoende tokens." + }, + "invalidAddress": { + "message": "Ongeldig adres" + }, + "invalidAddressRecipient": { + "message": "Het adres van de ontvanger is ongeldig" + }, + "invalidGasParams": { + "message": "Ongeldige gasparameters" + }, + "invalidInput": { + "message": "Ongeldige invoer." + }, + "invalidRequest": { + "message": "ongeldig verzoek" + }, + "invalidRPC": { + "message": "Ongeldige RPC-URI" + }, + "jsonFail": { + "message": "Er is iets fout gegaan. Zorg ervoor dat uw JSON-bestand correct is opgemaakt." + }, + "jsonFile": { + "message": "JSON-bestand", + "description": "formaat voor het importeren van een account" + }, + "kovan": { + "message": "Kovan-testnetwerk" + }, + "knowledgeDataBase": { + "message": "Bezoek onze Knowledge Base" + }, + "lessThanMax": { + "message": "moet kleiner zijn dan of gelijk zijn aan $1.", + "description": "helper voor het invoeren van hex als decimale invoer" + }, + "likeToAddTokens": { + "message": "Wil je deze tokens toevoegen?" + }, + "limit": { + "message": "Begrenzing" + }, + "loading": { + "message": "Bezig met laden..." + }, + "loadingTokens": { + "message": "Tokens laden ..." + }, + "localhost": { + "message": "Localhost 8545" + }, + "login": { + "message": "Log in" + }, + "logout": { + "message": "Uitloggen" + }, + "loose": { + "message": "Los" + }, + "loweCaseWords": { + "message": "zaadwoorden hebben alleen kleine letters" + }, + "mainnet": { + "message": "belangrijkste ethereum-netwerk" + }, + "message": { + "message": "Bericht" + }, + "metamaskDescription": { + "message": "MetaMask is een veilige identiteitskluis voor Ethereum." + }, + "min": { + "message": "Minimum" + }, + "myAccounts": { + "message": "Mijn accounts" + }, + "mustSelectOne": { + "message": "Moet ten minste één token selecteren." + }, + "needEtherInWallet": { + "message": "Om te communiceren met gedecentraliseerde applicaties met MetaMask, heb je Ether nodig in je portemonnee." + }, + "needImportFile": { + "message": "U moet een bestand selecteren om te importeren.", + "description": "Gebruiker is belangrijk een account en moet een bestand toevoegen om door te gaan" + }, + "needImportPassword": { + "message": "U moet een wachtwoord invoeren voor het geselecteerde bestand.", + "description": "Wachtwoord en bestand nodig om een account te importeren" + }, + "negativeETH": { + "message": "Kan geen negatieve hoeveelheden ETH verzenden." + }, + "networks": { + "message": "netwerken" + }, + "newAccount": { + "message": "Nieuw account" + }, + "newAccountNumberName": { + "message": "Account $1", + "description": "Standaardnaam van de volgende account die moet worden aangemaakt op het scherm voor het maken van een account" + }, + "newContract": { + "message": "Nieuw contract" + }, + "newPassword": { + "message": "Nieuw wachtwoord (min 8 tekens)" + }, + "newRecipient": { + "message": "Nieuwe ontvanger" + }, + "newRPC": { + "message": "Nieuwe RPC-URL" + }, + "next": { + "message": "volgende" + }, + "noAddressForName": { + "message": "Er is geen adres ingesteld voor deze naam." + }, + "noDeposits": { + "message": "Geen aanbetalingen ontvangen" + }, + "noTransactionHistory": { + "message": "Geen transactiegeschiedenis." + }, + "noTransactions": { + "message": "Geen transacties" + }, + "notStarted": { + "message": "Niet begonnen" + }, + "oldUI": { + "message": "Oude gebruikersinterface" + }, + "oldUIMessage": { + "message": "U bent teruggekeerd naar de oude gebruikersinterface. U kunt terugschakelen naar de nieuwe gebruikersinterface via de optie in het vervolgkeuzemenu in de rechterbovenhoek." + }, + "or": { + "message": "of", + "description": "keuze tussen het maken of importeren van een nieuw account" + }, + "passwordCorrect": { + "message": "Zorg ervoor dat uw wachtwoord correct is." + }, + "passwordMismatch": { + "message": "wachtwoorden komen niet overeen", + "description": "bij het maken van het wachtwoord kwamen de twee nieuwe wachtwoordvelden niet overeen" + }, + "passwordShort": { + "message": "wachtwoord niet lang genoeg", + "description": "bij het maken van het wachtwoord is het wachtwoord niet lang genoeg om veilig te zijn" + }, + "pastePrivateKey": { + "message": "Plak hier uw privésleutelstring:", + "description": "Voor het importeren van een account vanaf een privésleutel" + }, + "pasteSeed": { + "message": "Plak je zaadzin hier!" + }, + "personalAddressDetected": { + "message": "Persoonlijk adres gedetecteerd. Voer het tokencontractadres in." + }, + "pleaseReviewTransaction": { + "message": "Controleer uw transactie." + }, + "privacyMsg": { + "message": "Privacybeleid" + }, + "privateKey": { + "message": "Prive sleutel", + "description": "selecteer dit type bestand om te gebruiken om een account te importeren" + }, + "privateKeyWarning": { + "message": "Waarschuwing: open deze sleutel nooit. Iedereen met uw privésleutels kan stelen van alle items in uw account." + }, + "privateNetwork": { + "message": "Prive netwerk" + }, + "qrCode": { + "message": "QR-code weergeven" + }, + "readdToken": { + "message": "U kunt dit token in de toekomst weer toevoegen door naar \"Token toevoegen\" te gaan in het menu met accountopties." + }, + "readMore": { + "message": "Lees hier meer." + }, + "readMore2": { + "message": "Lees verder." + }, + "receive": { + "message": "Te ontvangen" + }, + "recipientAddress": { + "message": "Geadresseerde adres" + }, + "refundAddress": { + "message": "Uw teruggave adres" + }, + "rejected": { + "message": "Verworpen" + }, + "resetAccount": { + "message": "Account opnieuw instellen" + }, + "restoreFromSeed": { + "message": "Herstel van zaaduitdrukking" + }, + "required": { + "message": "Verplicht" + }, + "retryWithMoreGas": { + "message": "Probeer hier opnieuw met een hogere gasprijs" + }, + "revealSeedWords": { + "message": "Onthul zaadwoorden" + }, + "revealSeedWordsWarning": { + "message": "Herstel je zaadwoorden niet op een openbare plaats! Deze woorden kunnen worden gebruikt om al uw accounts te stelen." + }, + "revert": { + "message": "terugkeren" + }, + "rinkeby": { + "message": "Rinkeby testnetwerk" + }, + "ropsten": { + "message": "Ropsten testnetwerk" + }, + "sampleAccountName": { + "message": "Bijv. Mijn nieuwe account", + "description": "Help de gebruiker begrip te ontwikkelen van het toevoegen van een door mensen leesbare naam aan zijn of haar account" + }, + "save": { + "message": "Opslaan" + }, + "saveAsFile": { + "message": "Sla op als bestand", + "description": "Account export proces" + }, + "saveSeedAsFile": { + "message": "Bewaar zaadwoorden als bestand" + }, + "search": { + "message": "Zoeken" + }, + "secretPhrase": { + "message": "Voer hier je geheime twaalfwoordfrase in om je kluis te herstellen." + }, + "seedPhraseReq": { + "message": "zaadzinnen zijn 12 woorden lang" + }, + "select": { + "message": "kiezen" + }, + "selectCurrency": { + "message": "selecteer valuta" + }, + "selectService": { + "message": "Selecteer Service" + }, + "selectType": { + "message": "Selecteer type" + }, + "send": { + "message": "Sturen" + }, + "sendETH": { + "message": "Verzend ETH" + }, + "sendTokens": { + "message": "Stuur tokens" + }, + "sendTokensAnywhere": { + "message": "Stuur tokens naar iedereen met een Ethereum-account" + }, + "settings": { + "message": "instellingen" + }, + "shapeshiftBuy": { + "message": "Koop met Shapeshift" + }, + "showPrivateKeys": { + "message": "Privésleutels weergeven" + }, + "showQRCode": { + "message": "QR-code weergeven" + }, + "sign": { + "message": "Teken" + }, + "signMessage": { + "message": "Teken bericht" + }, + "signNotice": { + "message": "Het ondertekenen van dit bericht kan hebben \ngevaarlijke bijwerkingen. Meld alleen berichten van \nsites die u volledig vertrouwt met uw volledige account.\n Deze gevaarlijke methode wordt in een toekomstige versie verwijderd." + }, + "sigRequest": { + "message": "Ondertekeningsverzoek" + }, + "sigRequested": { + "message": "Handtekening aangevraagd" + }, + "spaceBetween": { + "message": "er kan alleen een spatie tussen woorden zijn" + }, + "status": { + "message": "staat" + }, + "stateLogs": { + "message": "Staatslogboeken" + }, + "stateLogsDescription": { + "message": "Staatslogboeken bevatten uw openbare accountadressen en verzonden transacties." + }, + "submit": { + "message": "voorleggen" + }, + "supportCenter": { + "message": "Bezoek ons ondersteuningscentrum" + }, + "symbolBetweenZeroTen": { + "message": "Het symbool moet tussen 0 en 10 tekens lang zijn." + }, + "takesTooLong": { + "message": "Duurt te lang?" + }, + "terms": { + "message": "Gebruiksvoorwaarden" + }, + "testFaucet": { + "message": "Test de kraan" + }, + "to": { + "message": "Naar" + }, + "toETHviaShapeShift": { + "message": "$1 tot ETH via ShapeShift", + "description": "systeem zal het aanbetalingstype invullen bij het begin van het bericht" + }, + "tokenAddress": { + "message": "Token-adres" + }, + "tokenAlreadyAdded": { + "message": "Token is al toegevoegd." + }, + "tokenBalance": { + "message": "Uw tokensaldo is:" + }, + "tokenSelection": { + "message": "Zoek naar tokens of selecteer uit onze lijst met populaire tokens." + }, + "tokenSymbol": { + "message": "Token Symbol" + }, + "tokenWarning1": { + "message": "Houd de tokens bij die je hebt gekocht met je MetaMask-account. Als je tokens met een ander account hebt gekocht, worden die tokens hier niet weergegeven." + }, + "total": { + "message": "Totaal" + }, + "transactions": { + "message": "transacties" + }, + "transactionMemo": { + "message": "Transactiememo (optioneel)" + }, + "transactionNumber": { + "message": "Transactie nummer" + }, + "transfers": { + "message": "transfers" + }, + "troubleTokenBalances": { + "message": "We hadden problemen bij het laden van uw tokenbalansen. Je kunt ze bekijken", + "description": "Gevolgd door een link (hier) om tegensaldi te bekijken" + }, + "twelveWords": { + "message": "Deze 12 woorden zijn de enige manier om uw MetaMask-accounts te herstellen.\nBewaar ze ergens veilig en geheim." + }, + "typePassword": { + "message": "Typ uw wachtwoord" + }, + "uiWelcome": { + "message": "Welkom bij de nieuwe gebruikersinterface (bèta)" + }, + "uiWelcomeMessage": { + "message": "U gebruikt nu de nieuwe gebruikersinterface van Metamask. Kijk rond, probeer nieuwe functies uit zoals het verzenden van tokens en laat ons weten of u problemen ondervindt." + }, + "unavailable": { + "message": "Niet beschikbaar" + }, + "unknown": { + "message": "Onbekend" + }, + "unknownNetwork": { + "message": "Onbekend privénetwerk" + }, + "unknownNetworkId": { + "message": "Onbekende netwerk-ID" + }, + "uriErrorMsg": { + "message": "Voor URI's is het juiste HTTP / HTTPS-voorvoegsel vereist." + }, + "usaOnly": { + "message": "Alleen in de VS.", + "description": "Het gebruik van deze uitwisseling is beperkt tot mensen in de VS." + }, + "usedByClients": { + "message": "Gebruikt door verschillende klanten" + }, + "useOldUI": { + "message": "Gebruik de oude gebruikersinterface" + }, + "validFileImport": { + "message": "U moet een geldig bestand selecteren om te importeren." + }, + "vaultCreated": { + "message": "Vault gemaakt" + }, + "viewAccount": { + "message": "Bekijk account" + }, + "visitWebSite": { + "message": "Bezoek onze website" + }, + "warning": { + "message": "Waarschuwing" + }, + "welcomeBeta": { + "message": "Welkom bij MetaMask Beta" + }, + "whatsThis": { + "message": "Wat is dit?" + }, + "yourSigRequested": { + "message": "Uw handtekening wordt aangevraagd" + }, + "youSign": { + "message": "U ondertekent" + } +} diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json new file mode 100644 index 000000000..29d63be02 --- /dev/null +++ b/app/_locales/ph/messages.json @@ -0,0 +1,609 @@ +{ + "accept": { + "message": "Tanggapin" + }, + "account": { + "message": "Account" + }, + "accountDetails": { + "message": "Detalye ng Account" + }, + "accountName": { + "message": "Pangalan ng Account" + }, + "address": { + "message": "Address" + }, + "addToken": { + "message": "Magdagdag ng Token" + }, + "amount": { + "message": "Halaga" + }, + "amountPlusGas": { + "message": "Halaga + Gas" + }, + "appDescription": { + "message": "Ethereum Browser Extension", + "description": "Ang deskripsyon ng application" + }, + "appName": { + "message": "MetaMask", + "description": "Ang pangalan ng application" + }, + "attemptingConnect": { + "message": "Sinusubukang kumonekta sa blockchain." + }, + "available": { + "message": "Magagamit" + }, + "back": { + "message": "Bumalik" + }, + "balance": { + "message": "Balanse:" + }, + "balanceIsInsufficientGas": { + "message": "Kulang ang balanse para sa kasalukuyang gas total" + }, + "beta": { + "message": "BETA" + }, + "betweenMinAndMax": { + "message": "dapat mas malaki o katumbas ng $1 at mas mababa o katumbas ng $2.", + "description": "helper para sa pag-input ng hex bilang decimal input" + }, + "borrowDharma": { + "message": "Humiram sa Dharma (Beta)" + }, + "buy": { + "message": "Bumili" + }, + "buyCoinbase": { + "message": "Bumili sa Coinbase" + }, + "buyCoinbaseExplainer": { + "message": "Ang Coinbase ang pinakasikat na paraan upang bumili at magbenta ng bitcoin, ethereum, at litecoin sa buong mundo." + }, + "cancel": { + "message": "Kanselahin" + }, + "clickCopy": { + "message": "I-click upang Makopya" + }, + "confirm": { + "message": "Tiyakin" + }, + "confirmContract": { + "message": "Tiyakin ang Contract" + }, + "confirmPassword": { + "message": "Tiyakin ang Password" + }, + "confirmTransaction": { + "message": "Tiyakin ang Transaksyon" + }, + "continueToCoinbase": { + "message": "Magpatuloy sa Coinbase" + }, + "contractDeployment": { + "message": "Pag-deploy ng Contract" + }, + "conversionProgress": { + "message": "Isinasagawa ang conversion" + }, + "copiedButton": { + "message": "Kinopya" + }, + "copiedClipboard": { + "message": "Kinopya sa Clipboard" + }, + "copiedExclamation": { + "message": "Kinopya!" + }, + "copy": { + "message": "Kinopya" + }, + "copyToClipboard": { + "message": "Kinopya sa clipboard" + }, + "copyButton": { + "message": " Kinopya " + }, + "copyPrivateKey": { + "message": "Ito ang iyong private key (i-click upang makopya)" + }, + "create": { + "message": "Gumawa" + }, + "createAccount": { + "message": "Gumawa ng Account" + }, + "createDen": { + "message": "Gumawa" + }, + "crypto": { + "message": "Crypto", + "description": "Type ng exchange (cryptocurrencies)" + }, + "customGas": { + "message": "I-customize ang Gas" + }, + "customize": { + "message": "I-customize" + }, + "customRPC": { + "message": "Custom RPC" + }, + "defaultNetwork": { + "message": "Ang default network para sa Ether transactions ay ang Main Net." + }, + "denExplainer": { + "message": "Ang iyong DEN ang nagsisilbing password-encrypted storage mo sa loob ng MetaMask." + }, + "deposit": { + "message": "Deposito" + }, + "depositBTC": { + "message": "I-deposito ang iyong BTC sa address na ito:" + }, + "depositCoin": { + "message": "I-deposito ang iyong $1 sa address na ito", + "description": "Sinasabihan ang user kung ano ang coin na kanilang pinili para I-deposito gamit ang shapeshift" + }, + "depositEth": { + "message": "I-deposito ang Eth" + }, + "depositEther": { + "message": "I-deposito ang Ether" + }, + "depositFiat": { + "message": "I-deposito ang Fiat" + }, + "depositFromAccount": { + "message": "I-deposito mula sa ibang account" + }, + "depositShapeShift": { + "message": "I-deposito gamit ang ShapeShift" + }, + "depositShapeShiftExplainer": { + "message": "Kung ikaw ay nagmamay-ari ng iba pang cryptocurrencies, pwede kang mag-trade at mag-deposito ng Ether diretso sa iyong MetaMask wallet. Hindi mo na kailangan ng account." + }, + "details": { + "message": "Detalye" + }, + "directDeposit": { + "message": "Direktang Deposito" + }, + "directDepositEther": { + "message": "Direktang I-deposito ang Ether" + }, + "directDepositEtherExplainer": { + "message": "Kung ika ay mayroon nang Ether, ang pinakamabilis na paraan upang makuha ang Ether sa iyong bagong wallet ay sa pamamagitan ng direktang deposito." + }, + "done": { + "message": "Tapos na" + }, + "edit": { + "message": "I-edit" + }, + "editAccountName": { + "message": "I-edit ang Pangalang ng Account" + }, + "encryptNewDen": { + "message": "I-encrypt ang iyong bagong DEN" + }, + "enterPassword": { + "message": "I-enter ang password" + }, + "etherscanView": { + "message": "Tingnan ang account sa Etherscan" + }, + "exchangeRate": { + "message": "Exchange Rate" + }, + "exportPrivateKey": { + "message": "I-export ang Private Key" + }, + "exportPrivateKeyWarning": { + "message": "I-export ang private keys at intindihin ang panganib na kasama nito." + }, + "failed": { + "message": "Nabigo" + }, + "fiat": { + "message": "FIAT", + "description": "Type ng exchange" + }, + "fileImportFail": { + "message": "Hindi gumagana ang file import? I-click ito!", + "description": "Tinutulungan ang user na i-import ang kanilang account mula sa JSON file" + }, + "from": { + "message": "Mula sa" + }, + "fromShapeShift": { + "message": "Mula sa ShapeShift" + }, + "gas": { + "message": "Gas", + "description": "Maikling indikasyon ng gas cost" + }, + "gasFee": { + "message": "Gas Fee" + }, + "gasLimit": { + "message": "Gas Limit" + }, + "gasLimitCalculation": { + "message": "Kinalkula namin ang iminungkahing gas limit base sa network success rates." + }, + "gasLimitRequired": { + "message": "Kailangan ang Gas Limit" + }, + "gasLimitTooLow": { + "message": "Ang gas limit ay hindi dabat bababa sa 21000" + }, + "gasPrice": { + "message": "Gas Price (GWEI)" + }, + "gasPriceCalculation": { + "message": "Kinalkula namin ang iminungkahing gas prices base sa network success rates." + }, + "gasPriceRequired": { + "message": "Kailangan ang Gas Price" + }, + "getEther": { + "message": "Kumuha ng Ether" + }, + "getEtherFromFaucet": { + "message": "Kumuha ng Ether mula sa faucet para sa $1", + "description": "Ipinapakita ang pangalan ng network para sa Ether faucet" + }, + "greaterThanMin": { + "message": "dapat mas malaki o katumbas ng $1.", + "description": "helper para sa pag-input ng hex bilang decimal input" + }, + "here": { + "message": "i-click ito", + "description": "tulad ng -i-click dito- para sa mas maraming impormasyon (kasama ng troubleTokenBalances)" + }, + "hide": { + "message": "Itago" + }, + "hideToken": { + "message": "Itago ang Token" + }, + "hideTokenPrompt": { + "message": "Itago ang Token?" + }, + "howToDeposit": { + "message": "Paano mo gustong mag-deposito ng Ether?" + }, + "import": { + "message": "I-import", + "description": "Button para i-import ang account mula sa napiling file" + }, + "importAccount": { + "message": "I-import ang Account" + }, + "importAnAccount": { + "message": "I-import ang account" + }, + "importDen": { + "message": "I-import ang Existing DEN" + }, + "imported": { + "message": "Na-import na", + "description": "status na nagpapakita na ang account ay lubos na na-load sa keyring" + }, + "infoHelp": { + "message": "Impormasyon at Tulong" + }, + "invalidAddress": { + "message": "Invalid ang address" + }, + "invalidGasParams": { + "message": "Invalid ang Gas Parameters" + }, + "invalidInput": { + "message": "Invalid ang input." + }, + "invalidRequest": { + "message": "Invalid ang Request" + }, + "jsonFile": { + "message": "JSON File", + "description": "format para sa pag-import ng account" + }, + "kovan": { + "message": "Kovan Test Network" + }, + "lessThanMax": { + "message": "dapat mas mababa o katumbas ng $1.", + "description": "helper para sa pag-input ng hex bilang decimal input" + }, + "limit": { + "message": "Limitasyon" + }, + "loading": { + "message": "Naglo-load..." + }, + "loadingTokens": { + "message": "Naglo-load ang Tokens..." + }, + "localhost": { + "message": "Localhost 8545" + }, + "logout": { + "message": "Log out" + }, + "loose": { + "message": "Loose" + }, + "mainnet": { + "message": "Main Ethereum Network" + }, + "message": { + "message": "Mensahe" + }, + "min": { + "message": "Minimum" + }, + "myAccounts": { + "message": "Aking mga Account" + }, + "needEtherInWallet": { + "message": "Upang makipag-ugnayan sa decentralized applications gamit ang MetaMask, kakailanganin mo ng Ether sa iyong wallet." + }, + "needImportFile": { + "message": "Dapat kang pumili ng file para i-import.", + "description": "Ang user ay nag-iimport ng account at kailangan magdagdag ng file upang tumuloy" + }, + "needImportPassword": { + "message": "Dapat mong i-enter ang password para sa napiling file.", + "description": "Password at file na kailangan upang ma-import ang account" + }, + "networks": { + "message": "Networks" + }, + "newAccount": { + "message": "Bagong Account" + }, + "newAccountNumberName": { + "message": "Account $1", + "description": "Ang default na pangalan ng susunod na account na gagawin sa create account screen" + }, + "newContract": { + "message": "Bagong Contract" + }, + "newPassword": { + "message": "Bagong Password (min 8 chars)" + }, + "newRecipient": { + "message": "Bagong Recipient" + }, + "next": { + "message": "Sunod" + }, + "noAddressForName": { + "message": "Walang naka-set na address para sa pangalang ito." + }, + "noDeposits": { + "message": "Walang natanggap na mga deposito" + }, + "noTransactionHistory": { + "message": "Walang kasaysayan ng transaksyon." + }, + "noTransactions": { + "message": "Walang mga Transaksyon" + }, + "notStarted": { + "message": "Hindi Sinimulan" + }, + "oldUI": { + "message": "Lumang UI" + }, + "oldUIMessage": { + "message": "Ikaw ay bumalik sa lumang UI. Maaari kang bumalik sa bagong UI mula sa isang opsyon sa dropdown menu na matatagpuan sa bandang taas at kanan." + }, + "or": { + "message": "o", + "description": "Pagpili sa pagitan ng paggawa of pag-import ng bagong account" + }, + "passwordMismatch": { + "message": "hindi nagtugma ang mga password", + "description": "Sa proseso ng paggawa ng password, ang dalawang password fields ay hindi nagtugma" + }, + "passwordShort": { + "message": "hindi sapat ang haba ng password", + "description": "Sa proseso ng paggawa ng password, ang password ay hindi in password creation process, hind sapat ang haba ng password upang maging ligtas" + }, + "pastePrivateKey": { + "message": "I-paste dito ang iyong private key string:", + "description": "Para sa pag-import ng account mula sa private key" + }, + "pasteSeed": { + "message": "I-paste dito ang iyong seed phrase!" + }, + "pleaseReviewTransaction": { + "message": "Mangyaring suriin ang iyong transaksyon." + }, + "privateKey": { + "message": "Private Key", + "description": "Piliin ang ganitong type ng file upang gamitin sa pag-import ng account" + }, + "privateKeyWarning": { + "message": "Babala: Huwag sabihin sa kahit na sino ang key na ito. Maaring makuha at manakaw ng sinumang nakakaalam ng iyong private key ang mga assets sa iyong account." + }, + "privateNetwork": { + "message": "Pribadong Network" + }, + "qrCode": { + "message": "Ipakita ang QR Code" + }, + "readdToken": { + "message": "Upang muling idagdag ang token na ito, pumunta sa “Magdagdag ng Token” sa options menu ng iyong account." + }, + "readMore": { + "message": "Alamin ang iba pang impormasyon dito." + }, + "receive": { + "message": "Tanggapin" + }, + "recipientAddress": { + "message": "Address ng Tatanggap" + }, + "refundAddress": { + "message": "Ang Iyong Refund Address" + }, + "rejected": { + "message": "Tinanggihan" + }, + "required": { + "message": "Kailangan" + }, + "retryWithMoreGas": { + "message": "Muling subukan ng may mas mataas na gas price dito" + }, + "revert": { + "message": "Ibalik" + }, + "rinkeby": { + "message": "Rinkeby Test Network" + }, + "ropsten": { + "message": "Ropsten Test Network" + }, + "sampleAccountName": { + "message": "Halimbawa: Ang aking bagong account", + "description": "Tulungan ang user na intindihin ang konsepto ng pagdagdag ng human-readable name sa kanilang account" + }, + "save": { + "message": "I-save" + }, + "saveAsFile": { + "message": "I-save bilang File", + "description": "Proseso sa pag-export ng Account" + }, + "selectService": { + "message": "Piliin ang Service" + }, + "send": { + "message": "Magpadala" + }, + "sendTokens": { + "message": "Magpadala ng Tokens" + }, + "sendTokensAnywhere": { + "message": "Magpadala ng Tokens sa sinumang may Ethereum account" + }, + "settings": { + "message": "Mga Setting" + }, + "shapeshiftBuy": { + "message": "Bumili gamit ang Shapeshift" + }, + "showPrivateKeys": { + "message": "Ipakita ang Private Keys" + }, + "showQRCode": { + "message": "Ipakita ang QR Code" + }, + "sign": { + "message": "I-sign" + }, + "signMessage": { + "message": "I-sign ang mensahe" + }, + "signNotice": { + "message": "Ang pag-sign ng mensaheng ito ay maaring magdulot ng mapanganib na epekto. I-sign lamang ang mga mensahe mula sa mga site na pinagkakatiwalaan mo ng iyong account. Ang mapanganib na paraang ito ay aalisin sa isa sa mga susunod na bersyon. " + }, + "sigRequest": { + "message": "Hiling na Signature" + }, + "sigRequested": { + "message": "Hiniling ang Signature" + }, + "status": { + "message": "Istado" + }, + "submit": { + "message": "I-submit" + }, + "takesTooLong": { + "message": "Masyadong matagal?" + }, + "testFaucet": { + "message": "Test Faucet" + }, + "to": { + "message": "To" + }, + "toETHviaShapeShift": { + "message": "$1 sa ETH sa pamamagitan ng ShapeShift", + "description": "Pupunan ng system ang deposit type sa simula ng mensahe" + }, + "tokenBalance": { + "message": "Ang iyong Token Balance ay:" + }, + "total": { + "message": "Kabuuan" + }, + "transactionMemo": { + "message": "Memo ng transaksyon (opsyonal)" + }, + "transactionNumber": { + "message": "Numero ng Transaksyon" + }, + "transfers": { + "message": "Mga Inilipat" + }, + "troubleTokenBalances": { + "message": "Nagkaroon kami ng problema sa paglo-load ng iyong mga balanseng token. Tingnan ito dito ", + "description": "Susundan ng link (dito) para tingnan ang token balances" + }, + "typePassword": { + "message": "I-type ang iyong Password" + }, + "uiWelcome": { + "message": "Maligayang pagdating sa Bagong UI (Beta)" + }, + "uiWelcomeMessage": { + "message": "Ginagamit mo na ngayon ang bagong MetaMask UI. I-explore at subukan ang mga bagong features tulad ng pagpapadala ng mga token, at ipaalam sa amin kung mayroon kang anumang mga isyu." + }, + "unavailable": { + "message": "Hindi Magagamit" + }, + "unknown": { + "message": "Hindi Alam" + }, + "unknownNetwork": { + "message": "Hindi Alam ang Pribadong Network" + }, + "unknownNetworkId": { + "message": "Hindi alam ang network ID" + }, + "usaOnly": { + "message": "USA lamang", + "description": "Ang paggamit ng exchange na ito ay limitado sa mga tao sa loob ng Estados Unidos" + }, + "usedByClients": { + "message": "Ginagamit ng iba't ibang mga clients" + }, + "viewAccount": { + "message": "Tingnan ang Account" + }, + "warning": { + "message": "Babala" + }, + "whatsThis": { + "message": "Ano ito?" + }, + "yourSigRequested": { + "message": "Hinihiling ang iyong signature" + }, + "youSign": { + "message": "Ikaw ay nagsa-sign" + } +} diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json new file mode 100644 index 000000000..c9eb178f9 --- /dev/null +++ b/app/_locales/pt/messages.json @@ -0,0 +1,819 @@ +{ + "accept": { + "message": "Aceitar" + }, + "account": { + "message": "Conta" + }, + "accountDetails": { + "message": "Detalhes da Conta" + }, + "accountName": { + "message": "Nome da Conta" + }, + "address": { + "message": "Endereço" + }, + "addCustomToken": { + "message": "Adicionar token customizada" + }, + "addToken": { + "message": "Adicionar Token" + }, + "addTokens": { + "message": "Adicionar Tokens" + }, + "amount": { + "message": "Valor" + }, + "amountPlusGas": { + "message": "Valor + Gas" + }, + "appDescription": { + "message": "Extensão para o browser de Ethereum", + "description": "A descrição da aplicação" + }, + "appName": { + "message": "MetaMask", + "description": "Nome da aplicação" + }, + "attemptingConnect": { + "message": "A tentar ligar à blockchain." + }, + "attributions": { + "message": "Atribuições" + }, + "available": { + "message": "Disponível" + }, + "back": { + "message": "Voltar" + }, + "balance": { + "message": "Saldo:" + }, + "balances": { + "message": "O meu saldo" + }, + "balanceIsInsufficientGas": { + "message": "Saldo insuficiente para a quantidade de gas total" + }, + "beta": { + "message": "BETA" + }, + "betweenMinAndMax": { + "message": "tem de ser maior ou igual a $1 e menor ou igual a $2.", + "description": "ajuda para introduzir hexadecimal como decimal" + }, + "blockiesIdenticon": { + "message": "Usar Blockies Identicon" + }, + "borrowDharma": { + "message": "Pedir Empréstimo Com Dharma (Beta)" + }, + "builtInCalifornia": { + "message": "MetaMask é desenhada e construída na California." + }, + "buy": { + "message": "Comprar" + }, + "buyCoinbase": { + "message": "Comprar no Coinbase" + }, + "buyCoinbaseExplainer": { + "message": "Coinbase é a forma mais conhecida para comprar e vender bitcoin, ethereum, e litecoin." + }, + "cancel": { + "message": "Cancelar" + }, + "classicInterface": { + "message": "Utilizar interface clássico" + }, + "clickCopy": { + "message": "Carregue para copiar" + }, + "confirm": { + "message": "Confirmar" + }, + "confirmContract": { + "message": "Confirmar Contrato" + }, + "confirmPassword": { + "message": "Confirmar Palavra-passe" + }, + "confirmTransaction": { + "message": "Confirmar Transação" + }, + "continue": { + "message": "Continuar" + }, + "continueToCoinbase": { + "message": "Continuar para o Coinbase" + }, + "contractDeployment": { + "message": "Distribuição do Contrato" + }, + "conversionProgress": { + "message": "Conversão em progresso" + }, + "copiedButton": { + "message": "Copiado" + }, + "copiedClipboard": { + "message": "Copiado para a Área de Transferência" + }, + "copiedExclamation": { + "message": "Copiado!" + }, + "copiedSafe": { + "message": "Já copiei para um lugar seguro" + }, + "copy": { + "message": "Copiar" + }, + "copyToClipboard": { + "message": "Copiar para o clipboard" + }, + "copyButton": { + "message": " Copiar " + }, + "copyPrivateKey": { + "message": "Esta é a sua chave privada (carregue para copiar)" + }, + "create": { + "message": "Criar" + }, + "createAccount": { + "message": "Criar Conta" + }, + "createDen": { + "message": "Criar" + }, + "crypto": { + "message": "Cripto", + "description": "Tipo de câmbio (criptomoedas)" + }, + "currentConversion": { + "message": "Taxa de Conversão Atual" + }, + "currentNetwork": { + "message": "Rede Atual" + }, + "customGas": { + "message": "Customizar Gas" + }, + "customize": { + "message": "Customizar" + }, + "customRPC": { + "message": "Customizar RPC" + }, + "decimalsMustZerotoTen": { + "message": "Decimais devem ser no mínimo 0 e não passar de 36." + }, + "decimal": { + "message": "Precisão em Decimais" + }, + "defaultNetwork": { + "message": "A rede pré definida para transações em Ether é a Main Net." + }, + "denExplainer": { + "message": " DEN é o armazenamento encriptado da sua palavra-passe no MetaMask." + }, + "deposit": { + "message": "Depósito" + }, + "depositBTC": { + "message": "Deposite as suas BTC no endereço abaixo:" + }, + "depositCoin": { + "message": "Deposite $1 no endereço abaixo", + "description": "Diz ao usuário que moeda selecionou para depositar com shapeshift" + }, + "depositEth": { + "message": "Depositar Eth" + }, + "depositEther": { + "message": "Depositar Ether" + }, + "depositFiat": { + "message": "Depositar moeda fiduciária" + }, + "depositFromAccount": { + "message": "Depositar de outra conta" + }, + "depositShapeShift": { + "message": "Depositar com ShapeShift" + }, + "depositShapeShiftExplainer": { + "message": "Se tem criptomoedas, pode trocar e depositar Ether diretamente na sua carteira MetaMask. Não precisa de conta." + }, + "details": { + "message": "Detalhes" + }, + "directDeposit": { + "message": "Depósito Direto" + }, + "directDepositEther": { + "message": "Depositar Diretamente Ether" + }, + "directDepositEtherExplainer": { + "message": "Se já tem Ether, a forma mais rápida de ficar com Ether na sua carteira é através de depósito direto." + }, + "done": { + "message": "Finalizado" + }, + "downloadStatelogs": { + "message": "Descarregar Registos de Estado" + }, + "edit": { + "message": "Editar" + }, + "editAccountName": { + "message": "Editar Nome da Conta" + }, + "emailUs": { + "message": "Fale connosco!" + }, + "encryptNewDen": { + "message": "Encripte o seu novo DEN" + }, + "enterPassword": { + "message": "Introduza palavra-passe" + }, + "enterPasswordConfirm": { + "message": "Introduza a sua palavra-passe para confirmar" + }, + "etherscanView": { + "message": "Ver conta no Etherscan" + }, + "exchangeRate": { + "message": "Taxa de Câmbio" + }, + "exportPrivateKey": { + "message": "Exportar Chave Privada" + }, + "exportPrivateKeyWarning": { + "message": "Exportar chaves privadas por sua conta e risco." + }, + "failed": { + "message": "Falhou" + }, + "fiat": { + "message": "FIAT", + "description": "Tipo de câmbio" + }, + "fileImportFail": { + "message": "A importação de ficheiro não está a funcionar? Carregue aqui!", + "description": "Ajuda usuários a importar as suas contas a partir de um ficheiro JSON" + }, + "followTwitter": { + "message": "Siga-nos no Twitter" + }, + "from": { + "message": "De" + }, + "fromToSame": { + "message": "Endereços De e Para não podem ser iguais" + }, + "fromShapeShift": { + "message": "De ShapeShift" + }, + "gas": { + "message": "Gas", + "description": "Indicação breve do custo de gas" + }, + "gasFee": { + "message": "Taxa de Gas" + }, + "gasLimit": { + "message": "Limite de Gas" + }, + "gasLimitCalculation": { + "message": "Calculamos o limite sugerido do gas com base nas taxas de sucesso da rede." + }, + "gasLimitRequired": { + "message": "Limite de Gas Necessário" + }, + "gasLimitTooLow": { + "message": "Limite de Gas deve ser no mínimo 21000" + }, + "generatingSeed": { + "message": "A gerar Seed..." + }, + "gasPrice": { + "message": "Preço Gas (GWEI)" + }, + "gasPriceCalculation": { + "message": "Calculamos o gas sugerido com base nas taxas de sucesso da rede." + }, + "gasPriceRequired": { + "message": "Preço Gas Necessário" + }, + "getEther": { + "message": "Obter Ether" + }, + "getEtherFromFaucet": { + "message": "Obter Ether de um faucet por $1", + "description": "Mostra nome da rede para faucet de Ether" + }, + "greaterThanMin": { + "message": "tem de ser maior ou igual a $1.", + "description": "ajuda para introduzir hexadecimal como decimal" + }, + "here": { + "message": "aqui", + "description": "como -clicar aqui- para mais informações (associado a troubleTokenBalances)" + }, + "hereList": { + "message": "Aqui está uma lista!!!!" + }, + "hide": { + "message": "Ocultar" + }, + "hideToken": { + "message": "Ocultar Token" + }, + "hideTokenPrompt": { + "message": "Ocultar Token?" + }, + "howToDeposit": { + "message": "Como gostaria de depositar Ether?" + }, + "holdEther": { + "message": "Permite ter ether & tokens, e serve como uma ponte para aplicações descentralizadas." + }, + "import": { + "message": "Importar", + "description": "Botão para importar uma conta de um ficheiro selecionado" + }, + "importAccount": { + "message": "Importar Conta" + }, + "importAccountMsg": { + "message":"Contas importadas não irão ser associadas com a frase seed da conta criada originalmente pelo MetaMask. Saiba mais sobre contas importadas." + }, + "importAnAccount": { + "message": "Importar uma conta" + }, + "importDen": { + "message": "Importar DEN Existente" + }, + "imported": { + "message": "Importado", + "description": "estado para mostrar que uma conta foi totalmente carregada para o keyring" + }, + "infoHelp": { + "message": "Informação & Ajuda" + }, + "insufficientFunds": { + "message": "Fundos insuficientes." + }, + "insufficientTokens": { + "message": "Tokens insuficientes." + }, + "invalidAddress": { + "message": "Endereço inválido" + }, + "invalidAddressRecipient": { + "message": "O endereço do destinatário é inválido " + }, + "invalidGasParams": { + "message": "Parâmetros para o Gas Inválidos" + }, + "invalidInput": { + "message": "Campo inválido." + }, + "invalidRequest": { + "message": "Pedido Inválido" + }, + "invalidRPC": { + "message": "RPC URI Inválido" + }, + "jsonFail": { + "message": "Ocorreu um erro. Por favor confirme que o seu ficheiro JSON está devidamente formatado." + }, + "jsonFile": { + "message": "Ficheiro JSON", + "description": "Formatar para importar uma conta" + }, + "kovan": { + "message": "Rede de Teste Kovan" + }, + "knowledgeDataBase": { + "message": "Visite o nosso Centro de Conhecimento" + }, + "lessThanMax": { + "message": "tem de ser menor ou igual a $1.", + "description": "ajuda para introduzir hexadecimal como decimal" + }, + "likeToAddTokens": { + "message": "Gostaria de adicionar estes tokens?" + }, + "limit": { + "message": "Limite" + }, + "loading": { + "message": "A carregar..." + }, + "loadingTokens": { + "message": "A carregar Tokens..." + }, + "localhost": { + "message": "Localhost 8545" + }, + "login": { + "message": "Entrar" + }, + "logout": { + "message": "Sair" + }, + "loose": { + "message": "Vago" + }, + "loweCaseWords": { + "message": "palavras da seed apenas têm caracteres minúsculos" + }, + "mainnet": { + "message": "Rede Principal de Ethereum" + }, + "message": { + "message": "Mensagem" + }, + "metamaskDescription": { + "message": "O MetaMask é um lugar seguro para guardar a sua identidade em em Ethereum." + }, + "min": { + "message": "Mínimo" + }, + "myAccounts": { + "message": "As minhas contas" + }, + "mustSelectOne": { + "message": "Deve escolher no mínimo 1 token." + }, + "needEtherInWallet": { + "message": "Para interagir com applicações descentralizadas usando MetaMask tem de ter Ether na sua carteira." + }, + "needImportFile": { + "message": "Deve selecionar um ficheiro para importar.", + "description": "O utilizador deve adicionar um ficheiro para continuar" + }, + "needImportPassword": { + "message": "Deve introduzir uma palavra-passe para o ficheiro selecionado.", + "description": "Palavra-passe e ficheiro necessários para importar uma conta" + }, + "negativeETH": { + "message": "Não é possível enviar valores negativos de ETH." + }, + "networks": { + "message": "Redes" + }, + "newAccount": { + "message": "Conta Nova" + }, + "newAccountNumberName": { + "message": "Conta $1", + "description": "Nome padrão da próxima conta a ser criado em Criar Conta" + }, + "newContract": { + "message": "Contrato Novo" + }, + "newPassword": { + "message": "Nova Palavra-passe (min 8 caracteres)" + }, + "newRecipient": { + "message": "Recipiente Novo" + }, + "newRPC": { + "message": "Novo RPC URL" + }, + "next": { + "message": "Próximo" + }, + "noAddressForName": { + "message": "Nenhum endereço foi estabelecido para este nome." + }, + "noDeposits": { + "message": "Sem depósitos recebidos" + }, + "noTransactionHistory": { + "message": "Sem histórico de transações." + }, + "noTransactions": { + "message": "Sem Transações" + }, + "notStarted": { + "message": "Não Iniciado" + }, + "oldUI": { + "message": "UI Antigo" + }, + "oldUIMessage": { + "message": "Voltou para o UI antigo. Pode reverter para o Novo UI através da opção no menu do topo direito." + }, + "or": { + "message": "ou", + "description": "opção entre criar ou importar uma nova conta" + }, + "passwordCorrect": { + "message": "Por favor confirme que a sua palavra-passe esteja correta." + }, + "passwordMismatch": { + "message": "as palavras-passe não coincidem", + "description": "no processo de criação da palavra-passe, as duas palavras-passe não coincidiram" + }, + "passwordShort": { + "message": "palavra-passe deve ser mais comprida", + "description": "no processo de criação da palavra-passe, a palavra-apasse não é longa o suficiente para ser segura" + }, + "pastePrivateKey": { + "message": "Cole aqui a sua chave privada:", + "description": "Para importar uma conta através da chave privada" + }, + "pasteSeed": { + "message": "Cole aqui a sua frase seed!" + }, + "personalAddressDetected": { + "message": "Endereço pessoal detectado. Introduza o endereço do contrato do token." + }, + "pleaseReviewTransaction": { + "message": "Por favor reveja a sua transação." + }, + "privacyMsg": { + "message": "Política de Privacidade" + }, + "privateKey": { + "message": "Chave Privada", + "description": "Selecione este tipo de ficheiro para importar uma conta" + }, + "privateKeyWarning": { + "message": "Atenção: Nunca revele esta chave. Qualquer pessoa com acesso à sua chave privada pode roubar os bens que esta contém." + }, + "privateNetwork": { + "message": "Rede Privada" + }, + "qrCode": { + "message": "Mostrar Código QR" + }, + "readdToken": { + "message": "Pode adicionar este token de novo clicando na opção “Adicionar token” no menu de opções da sua conta." + }, + "readMore": { + "message": "Ler mais aqui." + }, + "readMore2": { + "message": "Ler mais." + }, + "receive": { + "message": "Receber" + }, + "recipientAddress": { + "message": "Endereço do Destinatário" + }, + "refundAddress": { + "message": "O seu endereço de reembolso" + }, + "rejected": { + "message": "Rejeitado" + }, + "resetAccount": { + "message": "Reinicializar Conta" + }, + "restoreFromSeed": { + "message": "Restaurar a partir da frase seed" + }, + "required": { + "message": "Necessário" + }, + "retryWithMoreGas": { + "message": "Tentar novamente com um preço mais elevado aqui" + }, + "revealSeedWords": { + "message": "Revelar Palavras Seed" + }, + "revealSeedWordsWarning": { + "message": "Não revele as palavras seed num espaço público! Estas palavras podem ser usadas para roubar todas as suas contas." + }, + "revert": { + "message": "Reverter" + }, + "rinkeby": { + "message": "Rede de Teste Rinkeby" + }, + "ropsten": { + "message": "Rede de Teste Ropsten" + }, + "sampleAccountName": { + "message": "Ex. A minha conta nova", + "description": "Ajuda o utilizador a perceber o conceito de adicionar um nome legível à sua conta" + }, + "save": { + "message": "Guardar" + }, + "saveAsFile": { + "message": "Guardar como Ficheiro", + "description": "Processo de exportação de conta" + }, + "saveSeedAsFile": { + "message": "Guardar Palavras Seed como um Ficheiro" + }, + "search": { + "message": "Procurar" + }, + "secretPhrase": { + "message": "Introduza a sua frase secreta de 12 palavras para recuperar o seu ." + }, + "seedPhraseReq": { + "message": "seed phrases are 12 words long" + }, + "select": { + "message": "Selecionar" + }, + "selectCurrency": { + "message": "Selecionar Moeda" + }, + "selectService": { + "message": "Selecionar Serviço" + }, + "selectType": { + "message": "Selecionar Tipo" + }, + "send": { + "message": "Enviar" + }, + "sendETH": { + "message": "Enviar ETH" + }, + "sendTokens": { + "message": "Enviar Tokens" + }, + "sendTokensAnywhere": { + "message": "Enviar Tokens para qualquer pessoa com uma conta Ethereum" + }, + "settings": { + "message": "Definições" + }, + "shapeshiftBuy": { + "message": "Comprar com Shapeshift" + }, + "showPrivateKeys": { + "message": "Mostrar Chaves Privadas" + }, + "showQRCode": { + "message": "Mostrar Código QR" + }, + "sign": { + "message": "Assinar" + }, + "signMessage": { + "message": "Assinar Mensagem" + }, + "signNotice": { + "message": "Assinar esta mensagem pode ter \nefeitos laterais perigosos. Apenas assine mensagens de sites que \ntotalmente confia com a sua conta total.\n Este método perigoso será removido numa versão posterior." + }, + "sigRequest": { + "message": "Pedido de Assinatura" + }, + "sigRequested": { + "message": "Assinatura Pedida" + }, + "spaceBetween": { + "message": "só pode haver um espaço entre palavras" + }, + "status": { + "message": "Estado" + }, + "stateLogs": { + "message": "Registos de Estado" + }, + "stateLogsDescription": { + "message": "Registo de estado podem conter o seu endereço e transações enviadas da sua conta pública." + }, + "submit": { + "message": "Submeter" + }, + "supportCenter": { + "message": "Visitar o nosso Centro de Suporte" + }, + "symbolBetweenZeroTen": { + "message": "Símbolo deve conter entre 0 e 10 characters." + }, + "takesTooLong": { + "message": "A demorar muito?" + }, + "terms": { + "message": "Termos de Uso" + }, + "testFaucet": { + "message": "Faucet de Teste" + }, + "to": { + "message": "Para" + }, + "toETHviaShapeShift": { + "message": "$1 para ETH via ShapeShift", + "description": "o sistema irá preencher o tipo de depósito no início da mensagem" + }, + "tokenAddress": { + "message": "Endereço do Token" + }, + "tokenAlreadyAdded": { + "message": "Token já foi adicionado." + }, + "tokenBalance": { + "message": "O seu balanço é:" + }, + "tokenSelection": { + "message": "Procure por tokens ou seleccione da nossa lista de tokens populares." + }, + "tokenSymbol": { + "message": "Símbolo do Token" + }, + "tokenWarning1": { + "message": "Registe os tokens que comprou com a sua conta MetaMask. Se comprou tokens utilizando uma conta diferente, esses tokens não irão aparecer aqui." + }, + "total": { + "message": "Total" + }, + "transactions": { + "message": "transações" + }, + "transactionMemo": { + "message": "Notas da transação (opcional)" + }, + "transactionNumber": { + "message": "Número da Transação" + }, + "transfers": { + "message": "Transferências" + }, + "troubleTokenBalances": { + "message": "Tivemos um problema a carregar o balanço dos seus tokens. Pode vê-los em ", + "description": "Seguido de um link (aqui) para ver o balanço dos seus tokens" + }, + "twelveWords": { + "message": "Estas 12 palavras são a única forma de recuperar as suas contas na MetaMask.\nGuarde-as num local seguro e secreto." + }, + "typePassword": { + "message": "Digite a sua Palavra-passe" + }, + "uiWelcome": { + "message": "Bem-vindo ao seu Novo UI (Beta)" + }, + "uiWelcomeMessage": { + "message": "Está agora a usar o novo UI da MetaMask. Dê uma vista de olhos, experimenta as novas funcionalidades como enviar tokens e diga-nos se tiver algum problema." + }, + "unavailable": { + "message": "Indisponível" + }, + "unknown": { + "message": "Desconhecido" + }, + "unknownNetwork": { + "message": "Rede Privada Desconhecida" + }, + "unknownNetworkId": { + "message": "Identificador da rede desconhecido" + }, + "uriErrorMsg": { + "message": "Links requerem o prefixo HTTP/HTTPS apropriado." + }, + "usaOnly": { + "message": "Só nos EUA", + "description": "Usar esta taxa de câmbio está limitado a pessoas residentes nos EUA" + }, + "usedByClients": { + "message": "Utilizado por vários tipos de clientes" + }, + "useOldUI": { + "message": "Utilizar UI antigo" + }, + "validFileImport": { + "message": "Deve selecionar um ficheiro válido para importar." + }, + "vaultCreated": { + "message": "Cofre Criado" + }, + "viewAccount": { + "message": "Ver Conta" + }, + "visitWebSite": { + "message": "Visite o nosso site" + }, + "warning": { + "message": "Aviso" + }, + "welcomeBeta": { + "message": "Bem-vindo ao MetaMask Beta" + }, + "whatsThis": { + "message": "O que é isto?" + }, + "yourSigRequested": { + "message": "A sua assinatura está a ser pedida" + }, + "youSign": { + "message": "Está a assinar" + } +} diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json new file mode 100644 index 000000000..e3a1935f5 --- /dev/null +++ b/app/_locales/ru/messages.json @@ -0,0 +1,819 @@ +{ + "accept": { + "message": "Принять" + }, + "account": { + "message": "Аккаунт" + }, + "accountDetails": { + "message": "Детали Аккаунта" + }, + "accountName": { + "message": "Имя Пользователя" + }, + "address": { + "message": "Адрес" + }, + "addCustomToken": { + "message": "Добавить пользовательский токен" + }, + "addToken": { + "message": "Добавить токен" + }, + "addTokens": { + "message": "Добавить Токены" + }, + "amount": { + "message": "Количество" + }, + "amountPlusGas": { + "message": "Количество + газ" + }, + "appDescription": { + "message": "Расширение браузера для Ethereum", + "description": "The description of the application" + }, + "appName": { + "message": "MetaMask", + "description": "The name of the application" + }, + "attemptingConnect": { + "message": "Попытка подключиться к блокчейн сети." + }, + "attributions": { + "message": "Опознания" + }, + "available": { + "message": "Доступный" + }, + "back": { + "message": "Назад" + }, + "balance": { + "message": "Баланс:" + }, + "balances": { + "message": "Ваши балансы" + }, + "balanceIsInsufficientGas": { + "message": "Недостаточный баланс для текущего объема газа" + }, + "beta": { + "message": "БЕТА" + }, + "betweenMinAndMax": { + "message": "должно быть больше или равно $1 и меньше или равно $2.", + "description": "helper for inputting hex as decimal input" + }, + "blockiesIdenticon": { + "message": "Использовать Blockies Identicon" + }, + "borrowDharma": { + "message": "Заимствовать с Dharma (бета)" + }, + "builtInCalifornia": { + "message": "MetaMask спроектирован и построен в Калифорнии." + }, + "buy": { + "message": "Купить" + }, + "buyCoinbase": { + "message": "Купить на Coinbase" + }, + "buyCoinbaseExplainer": { + "message": "Coinbase - самый популярный в мире способ купить и продать биткойн, ethereum и litecoin." + }, + "cancel": { + "message": "Отмена" + }, + "classicInterface": { + "message": "Использовать классический интерфейс" + }, + "clickCopy": { + "message": "Нажмите, чтобы скопировать" + }, + "confirm": { + "message": "Подтвердить" + }, + "confirmContract": { + "message": "Подтвердить Контракт" + }, + "confirmPassword": { + "message": "Подтвердите Пароль" + }, + "confirmTransaction": { + "message": "Подтвердить Транзакцию" + }, + "continue": { + "message": "Продолжить" + }, + "continueToCoinbase": { + "message": "Продолжить в Coinbase" + }, + "contractDeployment": { + "message": "Развертывание контракта" + }, + "conversionProgress": { + "message": "Выполняется конверсия" + }, + "copiedButton": { + "message": "Скопировано" + }, + "copiedClipboard": { + "message": "Скопировано в буфер обмена" + }, + "copiedExclamation": { + "message": "Скопировано!" + }, + "copiedSafe": { + "message": "Я скопировал его где-то в безопасности" + }, + "copy": { + "message": "Скопировать" + }, + "copyToClipboard": { + "message": "Скопировать в буфер обмена" + }, + "copyButton": { + "message": " Скопировать " + }, + "copyPrivateKey": { + "message": "Это ваш личный ключ (нажмите, чтобы скопировать)" + }, + "create": { + "message": "Создать" + }, + "createAccount": { + "message": "Регистрация" + }, + "createDen": { + "message": "Создать" + }, + "crypto": { + "message": "Крипто", + "description": "Exchange type (cryptocurrencies)" + }, + "currentConversion": { + "message": "Текущая конверсия" + }, + "currentNetwork": { + "message": "Текущая сеть" + }, + "customGas": { + "message": "Настроить Газ" + }, + "customize": { + "message": "Настроить" + }, + "customRPC": { + "message": "Пользовательский RPC" + }, + "decimalsMustZerotoTen": { + "message": "Десятичные числа должны быть не менее 0, и не более 36." + }, + "decimal": { + "message": "Десятичные значения точности" + }, + "defaultNetwork": { + "message": "Сеть по умолчанию для транзакций Ether - это Main Net." + }, + "denExplainer": { + "message": "Ваш DEN - это ваше зашифрованное паролем хранилище в MetaMask." + }, + "deposit": { + "message": "Депозит" + }, + "depositBTC": { + "message": "Депозит BTC по адресу:" + }, + "depositCoin": { + "message": "Депозит $1 по указанному ниже адресу", + "description": "Tells the user what coin they have selected to deposit with shapeshift" + }, + "depositEth": { + "message": "Депозит Eth" + }, + "depositEther": { + "message": "Депозит Эфир" + }, + "depositFiat": { + "message": "Депозит с деньгами" + }, + "depositFromAccount": { + "message": "Депозит с другого счета" + }, + "depositShapeShift": { + "message": "Депозит с ShapeShift" + }, + "depositShapeShiftExplainer": { + "message": "Если у вас есть другие крипторесурсы, вы можете торговать и вносить Эфир непосредственно в кошелек MetaMask. Нет необходимости в аккаунте." + }, + "details": { + "message": "Детали" + }, + "directDeposit": { + "message": "Прямой Депозит" + }, + "directDepositEther": { + "message": "Прямой Депозит Эфира" + }, + "directDepositEtherExplainer": { + "message": "Если у вас уже есть Эфир, самый быстрый способ получить Эфир в вашем новом кошельке это прямым депозитом." + }, + "done": { + "message": "Готово" + }, + "downloadStatelogs": { + "message": "Загрузить логи статус" + }, + "edit": { + "message": "Редактировать" + }, + "editAccountName": { + "message": "Изменить Имя Аккаунта" + }, + "emailUs": { + "message": "Свяжитесь с нами по электронной почте!" + }, + "encryptNewDen": { + "message": "Шифруйте новый DEN" + }, + "enterPassword": { + "message": "Введите пароль" + }, + "enterPasswordConfirm": { + "message": "Введите свой пароль для подтверждения" + }, + "etherscanView": { + "message": "Просмотреть аккаунт на Etherscan" + }, + "exchangeRate": { + "message": "Обменный Курс" + }, + "exportPrivateKey": { + "message": "Экспорт закрытого ключа" + }, + "exportPrivateKeyWarning": { + "message": "Экспорт секретных ключей на свой страх и риск." + }, + "failed": { + "message": "Не смогли" + }, + "fiat": { + "message": "Бумажные деньги", + "description": "Exchange type" + }, + "fileImportFail": { + "message": "Ошибка импорта файлов? Кликните сюда!", + "description": "Helps user import their account from a JSON file" + }, + "followTwitter": { + "message": "Следуйте за нами на Twitter" + }, + "from": { + "message": "Из" + }, + "fromToSame": { + "message": "От и до адреса не могут быть одинаковым" + }, + "fromShapeShift": { + "message": "Из ShapeShift" + }, + "gas": { + "message": "Газ", + "description": "Short indication of gas cost" + }, + "gasFee": { + "message": "Плата за Газ" + }, + "gasLimit": { + "message": "Газовый Предел" + }, + "gasLimitCalculation": { + "message": "Мы рассчитываем предполагаемый предел газа на основе коэффициентов успешности сети." + }, + "gasLimitRequired": { + "message": "Требуется ограничение на Газ" + }, + "gasLimitTooLow": { + "message": "Предел газа должен быть не менее 21000" + }, + "generatingSeed": { + "message": "Создание Семян ..." + }, + "gasPrice": { + "message": "Цена на Газ (GWEI)" + }, + "gasPriceCalculation": { + "message": "Мы вычисляем предлагаемые цены на газ на основе коэффициентов успеха сети." + }, + "gasPriceRequired": { + "message": "Требуется цена на Газ" + }, + "getEther": { + "message": "Получить Эфир" + }, + "getEtherFromFaucet": { + "message": "Получите Эфир из крана $1", + "description": "Displays network name for Ether faucet" + }, + "greaterThanMin": { + "message": "должно быть больше или равно $1.", + "description": "helper for inputting hex as decimal input" + }, + "here": { + "message": "здесь", + "description": "as in -click here- for more information (goes with troubleTokenBalances)" + }, + "hereList": { + "message": "Вот список !!!!" + }, + "hide": { + "message": "Спрятать" + }, + "hideToken": { + "message": "Скрыть токен" + }, + "hideTokenPrompt": { + "message": "Скрыть токен?" + }, + "howToDeposit": { + "message": "Как бы вы хотели поместить Эфир?" + }, + "holdEther": { + "message": "Это позволяет вам использовать эфир и токены и служит мостом для децентрализованных приложений." + }, + "import": { + "message": "Импортировать", + "description": "Button to import an account from a selected file" + }, + "importAccount": { + "message": "Импорт Аккаунта" + }, + "importAccountMsg": { + "message": " Импортированные аккаунты не будут связаны с вашей первоначально созданным аккаунтом MetaMask. Подробнее о импортированных аккаунтах " + }, + "importAnAccount": { + "message": "Импортировать аккаунт" + }, + "importDen": { + "message": "Импорт существующих DEN" + }, + "imported": { + "message": "Импортирован", + "description": "status showing that an account has been fully loaded into the keyring" + }, + "infoHelp": { + "message": "Информация и Помощь" + }, + "insufficientFunds": { + "message": "Недостаточно средств." + }, + "insufficientTokens": { + "message": "Недостаточно токенов." + }, + "invalidAddress": { + "message": "Недействительный адрес" + }, + "invalidAddressRecipient": { + "message": "Недопустимый адрес получателя." + }, + "invalidGasParams": { + "message": "Недопустимые параметры Газа" + }, + "invalidInput": { + "message": "Неправильный ввод." + }, + "invalidRequest": { + "message": "Неверный Запрос" + }, + "invalidRPC": { + "message": "Недопустимый URI RPC" + }, + "jsonFail": { + "message": "Что-то пошло не так. Убедитесь, что ваш файл JSON правильно отформатирован." + }, + "jsonFile": { + "message": "Файл JSON", + "description": "format for importing an account" + }, + "kovan": { + "message": "Kovan тестовая сеть" + }, + "knowledgeDataBase": { + "message": "Посетите нашу базу знаний" + }, + "lessThanMax": { + "message": "должно быть меньше или равно $1.", + "description": "helper for inputting hex as decimal input" + }, + "likeToAddTokens": { + "message": "Вы хотите добавить эти токены?" + }, + "limit": { + "message": "Предел" + }, + "loading": { + "message": "Загрузка..." + }, + "loadingTokens": { + "message": "Загрузка токенов ..." + }, + "localhost": { + "message": "Локальный адрес 8545" + }, + "login": { + "message": "Авторизоваться" + }, + "logout": { + "message": "Выйти" + }, + "loose": { + "message": "Рыхлый" + }, + "loweCaseWords": { + "message": "семенные слова имеют только символы нижнего регистра" + }, + "mainnet": { + "message": "Основная сеть Ethereum" + }, + "message": { + "message": "Сообщение" + }, + "metamaskDescription": { + "message": "MetaMask - это безопасное хранилище для Ethereum." + }, + "min": { + "message": "Минимум" + }, + "myAccounts": { + "message": "Мои Аккаунты" + }, + "mustSelectOne": { + "message": "Необходимо выбрать не менее 1 токена." + }, + "needEtherInWallet": { + "message": "Чтобы взаимодействовать с децентрализованными приложениями с помощью MetaMask, вам понадобится Эфир в вашем кошельке." + }, + "needImportFile": { + "message": "Вы должны выбрать файл для импорта.", + "description": "User is important an account and needs to add a file to continue" + }, + "needImportPassword": { + "message": "Вы должны ввести пароль для выбранного файла.", + "description": "Password and file needed to import an account" + }, + "negativeETH": { + "message": "Невозможно отправить отрицательные количества ETH." + }, + "networks": { + "message": "Сети" + }, + "newAccount": { + "message": "Новый Аккаунт" + }, + "newAccountNumberName": { + "message": "Аккаунт $1", + "description": "Default name of next account to be created on create account screen" + }, + "newContract": { + "message": "Новый Контракт" + }, + "newPassword": { + "message": "Новый пароль (мин. 8 символов)" + }, + "newRecipient": { + "message": "Новый Получатель" + }, + "newRPC": { + "message": "Новый URL-адрес RPC" + }, + "next": { + "message": "Далее" + }, + "noAddressForName": { + "message": "Для этого имени не задан адрес." + }, + "noDeposits": { + "message": "Не было получено никаких депозитов" + }, + "noTransactionHistory": { + "message": "Нет истории транзакций." + }, + "noTransactions": { + "message": "Нет Транзакций" + }, + "notStarted": { + "message": "Не Начался" + }, + "oldUI": { + "message": "Старый Интерфейс" + }, + "oldUIMessage": { + "message": "Вы вернулись к старому интерфейсу. Вы можете вернуться к новому с помощью опции в раскрывающемся меню в правом верхнем углу." + }, + "or": { + "message": "или", + "description": "choice between creating or importing a new account" + }, + "passwordCorrect": { + "message": "Убедитесь, что ваш пароль правильный." + }, + "passwordMismatch": { + "message": "пароли не совпадают", + "description": "in password creation process, the two new password fields did not match" + }, + "passwordShort": { + "message": "пароль недостаточно длинный", + "description": "in password creation process, the password is not long enough to be secure" + }, + "pastePrivateKey": { + "message": "Вставьте свою личную строку:", + "description": "For importing an account from a private key" + }, + "pasteSeed": { + "message": "Вставьте здесь свою семенную фразу!" + }, + "personalAddressDetected": { + "message": "Персональный адрес обнаружен. Введите адрес контракта токена." + }, + "pleaseReviewTransaction": { + "message": "Проверьте транзакцию." + }, + "privacyMsg": { + "message": "Политика Конфиденциальности" + }, + "privateKey": { + "message": "Закрытый ключ", + "description": "select this type of file to use to import an account" + }, + "privateKeyWarning": { + "message": "Предупреждение: никогда не раскрывайте этот ключ. Любой, у кого есть ваши личные ключи, может украсть любые активы, хранящиеся в вашем аккаунте." + }, + "privateNetwork": { + "message": "Частная сеть" + }, + "qrCode": { + "message": "Показать QR-код" + }, + "readdToken": { + "message": "Вы можете добавить этот токен в будущем, перейдя в “Добавить токен” в меню параметров вашего аккаунта." + }, + "readMore": { + "message": "Подробнее читайте здесь." + }, + "readMore2": { + "message": "Прочитайте больше." + }, + "receive": { + "message": "Получить" + }, + "recipientAddress": { + "message": "Адрес Получателя" + }, + "refundAddress": { + "message": "Ваш Адрес Возврата" + }, + "rejected": { + "message": "Отклонено" + }, + "resetAccount": { + "message": "Сбросить аккаунт" + }, + "restoreFromSeed": { + "message": "Восстановить от семенной фразы" + }, + "required": { + "message": "Необходимо" + }, + "retryWithMoreGas": { + "message": "Повторите попытку с более высокой ценой на газ здесь" + }, + "revealSeedWords": { + "message": "Раскрыть семенные слова" + }, + "revealSeedWordsWarning": { + "message": "Не восстанавливайте семенные слова в общественном месте! Эти слова могут использоваться для кражи всех ваших аккаунтах." + }, + "revert": { + "message": "Откат" + }, + "rinkeby": { + "message": "Rinkeby тестовая сеть" + }, + "ropsten": { + "message": "Ropsten тестовая сеть" + }, + "sampleAccountName": { + "message": "Например, Мой новый аккаунт", + "description": "Help user understand concept of adding a human-readable name to their account" + }, + "save": { + "message": "Сохранить" + }, + "saveAsFile": { + "message": "Сохранить как Файл", + "description": "Account export process" + }, + "saveSeedAsFile": { + "message": "Сохранить Семенные Слова Как Файл" + }, + "search": { + "message": "Поиск" + }, + "secretPhrase": { + "message": "Введите свою секретную двенадцатисловную фразу здесь, чтобы восстановить хранилище." + }, + "seedPhraseReq": { + "message": "семенные фразы длиной 12 слов" + }, + "select": { + "message": "Выбрать" + }, + "selectCurrency": { + "message": "Выберите Валюту" + }, + "selectService": { + "message": "Выберите Сервис" + }, + "selectType": { + "message": "Выберите Тип" + }, + "send": { + "message": "Послать" + }, + "sendETH": { + "message": "Отправить ETH" + }, + "sendTokens": { + "message": "Отправить Токены" + }, + "sendTokensAnywhere": { + "message": "Отправить Токены кому-либо с аккаунтом Ethereum" + }, + "settings": { + "message": "Настройки" + }, + "shapeshiftBuy": { + "message": "Купить с помощью Shapeshift" + }, + "showPrivateKeys": { + "message": "Показать приватные ключи" + }, + "showQRCode": { + "message": "Показать QR-код" + }, + "sign": { + "message": "Знак" + }, + "signMessage": { + "message": "Нодписать сообщение" + }, + "signNotice": { + "message": "Подписание этого сообщения может иметь \nопасные побочные эффекты. Только подписывайте сообщения \nс сайтов, которым вы полностью доверяете своим аккаунтом. Этот опасный метод будет удален в будущей версии." + }, + "sigRequest": { + "message": "Запрос на подпись" + }, + "sigRequested": { + "message": "Подпись Запрошена" + }, + "spaceBetween": { + "message": "между словами может быть только пробел" + }, + "status": { + "message": "Статус" + }, + "stateLogs": { + "message": "Логи Статуса" + }, + "stateLogsDescription": { + "message": "Логи статуса содержат ваши общедоступные адреса и отправленные транзакции." + }, + "submit": { + "message": "Отправить" + }, + "supportCenter": { + "message": "Посетите наш Центр поддержки" + }, + "symbolBetweenZeroTen": { + "message": "Символ должен быть от 0 до 10 символов." + }, + "takesTooLong": { + "message": "Занимает слишком долго?" + }, + "terms": { + "message": "Условия Эксплуатации" + }, + "testFaucet": { + "message": "Тестовый Кран" + }, + "to": { + "message": "К" + }, + "toETHviaShapeShift": { + "message": "$1 в ETH через ShapeShift", + "description": "system will fill in deposit type in start of message" + }, + "tokenAddress": { + "message": "Адрес Токена" + }, + "tokenAlreadyAdded": { + "message": "Токен уже добавлен." + }, + "tokenBalance": { + "message": "Баланс Вашых Tокенов:" + }, + "tokenSelection": { + "message": "Поиск токенов или выбор из нашего списка популярных токенов." + }, + "tokenSymbol": { + "message": "Символ Токена" + }, + "tokenWarning1": { + "message": "Следите за токенами, которые вы купили с помощью аккаунта MetaMask. Если вы купили токены, используя другой аккаунт, эти токены здесь не появятся." + }, + "total": { + "message": "Всего" + }, + "transactions": { + "message": "транзакции" + }, + "transactionMemo": { + "message": "Транзакционная записка (необязательно)" + }, + "transactionNumber": { + "message": "Номер Транзакции" + }, + "transfers": { + "message": "Переводы" + }, + "troubleTokenBalances": { + "message": "У нас были проблемы с загрузкой ваших токенов. Вы можете просмотреть их ", + "description": "Followed by a link (here) to view token balances" + }, + "twelveWords": { + "message": "Эти 12 слов - единственный способ восстановить ваши учетные записи MetaMask.\nСохраните их где-нибудь в безопасности и в тайне." + }, + "typePassword": { + "message": "Введите Пароль" + }, + "uiWelcome": { + "message": "Добро пожаловать в новый интерфейс (бета-версия)" + }, + "uiWelcomeMessage": { + "message": "Теперь вы используете новый интерфейс Metamask. Осмотритесь, попробуйте новые функции, такие как отправку токенов, и сообщите нам, есть ли у вас какие-либо проблемы." + }, + "unavailable": { + "message": "Недоступен" + }, + "unknown": { + "message": "Неизвестный" + }, + "unknownNetwork": { + "message": "Неизвестная частная сеть" + }, + "unknownNetworkId": { + "message": "Неизвестный идентификатор сети" + }, + "uriErrorMsg": { + "message": "Для URI требуется соответствующий префикс HTTP / HTTPS." + }, + "usaOnly": { + "message": "Только США", + "description": "Using this exchange is limited to people inside the USA" + }, + "usedByClients": { + "message": "Используется различными клиентами" + }, + "useOldUI": { + "message": "Использовать старый интерфейс" + }, + "validFileImport": { + "message": "Вы должны выбрать действительный файл для импорта." + }, + "vaultCreated": { + "message": "Создано хранилище" + }, + "viewAccount": { + "message": "Посмотреть аккаунт" + }, + "visitWebSite": { + "message": "Посетите наш сайт" + }, + "warning": { + "message": "Предупреждение" + }, + "welcomeBeta": { + "message": "Добро пожаловать в MetaMask Beta" + }, + "whatsThis": { + "message": "Что это?" + }, + "yourSigRequested": { + "message": "Ваша подпись запрашивается" + }, + "youSign": { + "message": "Вы подписываете" + } +} diff --git a/app/manifest.json b/app/manifest.json index 6fcf6cd7c..0aac1c8df 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "4.2.0", + "version": "4.3.0", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", diff --git a/app/scripts/README.md b/app/scripts/README.md new file mode 100644 index 000000000..f5a907244 --- /dev/null +++ b/app/scripts/README.md @@ -0,0 +1,14 @@ +# Main MetaMask Code + +This folder contains the core-code. + +Currently, it is organized mostly based on file category, like: + +controllers, migrations, lib + +## Ongoing Task + +Refactor code-structure, thus the subsystems are reflected on the filesystem. + +### Examples + diff --git a/app/scripts/background.js b/app/scripts/background.js index ef5513ec7..8bd7766ad 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -42,6 +42,7 @@ const isIE = !!document.documentMode const isEdge = !isIE && !!window.StyleMedia let popupIsOpen = false +let notificationIsOpen = false let openMetamaskTabsIDs = {} // state persistence @@ -165,6 +166,11 @@ function setupController (initState) { } }) } + if (remotePort.name === 'notification') { + endOfStream(portStream, () => { + notificationIsOpen = false + }) + } } else { // communication with page const originDomain = urlUtil.parse(remotePort.sender.url).hostname @@ -207,7 +213,8 @@ function setupController (initState) { function triggerUi () { extension.tabs.query({ active: true }, (tabs) => { const currentlyActiveMetamaskTab = tabs.find(tab => openMetamaskTabsIDs[tab.id]) - if (!popupIsOpen && !currentlyActiveMetamaskTab) notificationManager.showPopup() + if (!popupIsOpen && !currentlyActiveMetamaskTab && !notificationIsOpen) notificationManager.showPopup() + notificationIsOpen = true }) } diff --git a/app/scripts/controllers/README.md b/app/scripts/controllers/README.md new file mode 100644 index 000000000..392c0457d --- /dev/null +++ b/app/scripts/controllers/README.md @@ -0,0 +1,4 @@ +# Controllers + +Different controllers (in the sense of *VC *View-Controller). + diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 9c2ca0dc8..3e3909361 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -6,7 +6,6 @@ const EthQuery = require('ethjs-query') const TransactionStateManager = require('../lib/tx-state-manager') const TxGasUtil = require('../lib/tx-gas-utils') const PendingTransactionTracker = require('../lib/pending-tx-tracker') -const createId = require('../lib/random-id') const NonceTracker = require('../lib/nonce-tracker') /* @@ -92,8 +91,8 @@ module.exports = class TransactionController extends EventEmitter { this.pendingTxTracker.on('tx:warning', (txMeta) => { this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning') }) + this.pendingTxTracker.on('tx:confirmed', (txId) => this._markNonceDuplicatesDropped(txId)) this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager)) - this.pendingTxTracker.on('tx:confirmed', this.txStateManager.setTxStatusConfirmed.bind(this.txStateManager)) this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => { if (!txMeta.firstRetryBlockNumber) { txMeta.firstRetryBlockNumber = latestBlockNumber @@ -186,14 +185,7 @@ module.exports = class TransactionController extends EventEmitter { // validate await this.txGasUtil.validateTxParams(txParams) // construct txMeta - const txMeta = { - id: createId(), - time: (new Date()).getTime(), - status: 'unapproved', - metamaskNetworkId: this.getNetwork(), - txParams: txParams, - loadingDefaults: true, - } + const txMeta = this.txStateManager.generateTxMeta({txParams}) this.addTx(txMeta) this.emit('newUnapprovedTx', txMeta) // add default tx params @@ -215,7 +207,6 @@ module.exports = class TransactionController extends EventEmitter { const txParams = txMeta.txParams // ensure value txMeta.gasPriceSpecified = Boolean(txParams.gasPrice) - txMeta.nonceSpecified = Boolean(txParams.nonce) let gasPrice = txParams.gasPrice if (!gasPrice) { gasPrice = this.getGasPrice ? this.getGasPrice() : await this.query.gasPrice() @@ -226,11 +217,17 @@ module.exports = class TransactionController extends EventEmitter { return await this.txGasUtil.analyzeGasUsage(txMeta) } - async retryTransaction (txId) { - this.txStateManager.setTxStatusUnapproved(txId) - const txMeta = this.txStateManager.getTx(txId) - txMeta.lastGasPrice = txMeta.txParams.gasPrice - this.txStateManager.updateTx(txMeta, 'retryTransaction: manual retry') + async retryTransaction (originalTxId) { + const originalTxMeta = this.txStateManager.getTx(originalTxId) + const lastGasPrice = originalTxMeta.txParams.gasPrice + const txMeta = this.txStateManager.generateTxMeta({ + txParams: originalTxMeta.txParams, + lastGasPrice, + loadingDefaults: false, + }) + this.addTx(txMeta) + this.emit('newUnapprovedTx', txMeta) + return txMeta } async updateTransaction (txMeta) { @@ -253,11 +250,9 @@ module.exports = class TransactionController extends EventEmitter { // wait for a nonce nonceLock = await this.nonceTracker.getNonceLock(fromAddress) // add nonce to txParams - const nonce = txMeta.nonceSpecified ? txMeta.txParams.nonce : nonceLock.nextNonce - if (nonce > nonceLock.nextNonce) { - const message = `Specified nonce may not be larger than account's next valid nonce.` - throw new Error(message) - } + // if txMeta has lastGasPrice then it is a retry at same nonce with higher + // gas price transaction and their for the nonce should not be calculated + const nonce = txMeta.lastGasPrice ? txMeta.txParams.nonce : nonceLock.nextNonce txMeta.txParams.nonce = ethUtil.addHexPrefix(nonce.toString(16)) // add nonce debugging information to txMeta txMeta.nonceDetails = nonceLock.nonceDetails @@ -314,6 +309,22 @@ module.exports = class TransactionController extends EventEmitter { // PRIVATE METHODS // + _markNonceDuplicatesDropped (txId) { + this.txStateManager.setTxStatusConfirmed(txId) + // get the confirmed transactions nonce and from address + const txMeta = this.txStateManager.getTx(txId) + const { nonce, from } = txMeta.txParams + const sameNonceTxs = this.txStateManager.getFilteredTxList({nonce, from}) + if (!sameNonceTxs.length) return + // mark all same nonce transactions as dropped and give i a replacedBy hash + sameNonceTxs.forEach((otherTxMeta) => { + if (otherTxMeta.id === txId) return + otherTxMeta.replacedBy = txMeta.hash + this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce') + this.txStateManager.setTxStatusDropped(otherTxMeta.id) + }) + } + _updateMemstore () { const unapprovedTxs = this.txStateManager.getUnapprovedTxList() const selectedAddressTxList = this.txStateManager.getFilteredTxList({ diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js index adaf60c65..1fcb7cf69 100644 --- a/app/scripts/lib/notification-manager.js +++ b/app/scripts/lib/notification-manager.js @@ -13,11 +13,12 @@ class NotificationManager { this._getPopup((err, popup) => { if (err) throw err + // Bring focus to chrome popup if (popup) { - // bring focus to existing popup + // bring focus to existing chrome popup extension.windows.update(popup.id, { focused: true }) } else { - // create new popup + // create new notification popup extension.windows.create({ url: 'notification.html', type: 'popup', @@ -29,6 +30,7 @@ class NotificationManager { } closePopup () { + // closes notification popup this._getPopup((err, popup) => { if (err) throw err if (!popup) return @@ -60,9 +62,8 @@ class NotificationManager { _getPopupIn (windows) { return windows ? windows.find((win) => { - return (win && win.type === 'popup' && - win.height === height && - win.width === width) + // Returns notification popup + return (win && win.type === 'popup') }) : null } diff --git a/app/scripts/lib/setupRaven.js b/app/scripts/lib/setupRaven.js index 42e48cb90..02c01b755 100644 --- a/app/scripts/lib/setupRaven.js +++ b/app/scripts/lib/setupRaven.js @@ -1,4 +1,4 @@ -const Raven = require('../vendor/raven.min.js') +const Raven = require('raven-js') const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' const PROD = 'https://3567c198f8a8412082d32655da2961d0@sentry.io/273505' const DEV = 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496' @@ -18,9 +18,35 @@ function setupRaven(opts) { ravenTarget = PROD } - Raven.config(ravenTarget, { + const client = Raven.config(ravenTarget, { release, - }).install() + transport: function(opts) { + // modify report urls + const report = opts.data + rewriteReportUrls(report) + // make request normally + client._makeRequest(opts) + }, + }) + client.install() return Raven } + +function rewriteReportUrls(report) { + // update request url + report.request.url = toMetamaskUrl(report.request.url) + // update exception stack trace + report.exception.values.forEach(item => { + item.stacktrace.frames.forEach(frame => { + frame.filename = toMetamaskUrl(frame.filename) + }) + }) +} + +function toMetamaskUrl(origUrl) { + const filePath = origUrl.split(location.origin)[1] + if (!filePath) return origUrl + const metamaskUrl = `metamask${filePath}` + return metamaskUrl +} diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js index 2eb006380..ad07c813f 100644 --- a/app/scripts/lib/tx-state-manager.js +++ b/app/scripts/lib/tx-state-manager.js @@ -1,9 +1,21 @@ const extend = require('xtend') const EventEmitter = require('events') const ObservableStore = require('obs-store') +const createId = require('./random-id') const ethUtil = require('ethereumjs-util') const txStateHistoryHelper = require('./tx-state-history-helper') +// STATUS METHODS + // statuses: + // - `'unapproved'` the user has not responded + // - `'rejected'` the user has responded no! + // - `'approved'` the user has approved the tx + // - `'signed'` the tx is signed + // - `'submitted'` the tx is sent to a server + // - `'confirmed'` the tx has been included in a block. + // - `'failed'` the tx failed for some reason, included on tx data. + // - `'dropped'` the tx nonce was already used + module.exports = class TransactionStateManager extends EventEmitter { constructor ({ initState, txHistoryLimit, getNetwork }) { super() @@ -16,6 +28,16 @@ module.exports = class TransactionStateManager extends EventEmitter { this.getNetwork = getNetwork } + generateTxMeta (opts) { + return extend({ + id: createId(), + time: (new Date()).getTime(), + status: 'unapproved', + metamaskNetworkId: this.getNetwork(), + loadingDefaults: true, + }, opts) + } + // Returns the number of txs for the current network. getTxCount () { return this.getTxList().length @@ -164,16 +186,6 @@ module.exports = class TransactionStateManager extends EventEmitter { }) } - // STATUS METHODS - // statuses: - // - `'unapproved'` the user has not responded - // - `'rejected'` the user has responded no! - // - `'approved'` the user has approved the tx - // - `'signed'` the tx is signed - // - `'submitted'` the tx is sent to a server - // - `'confirmed'` the tx has been included in a block. - // - `'failed'` the tx failed for some reason, included on tx data. - // get::set status // should return the status of the tx. @@ -202,7 +214,11 @@ module.exports = class TransactionStateManager extends EventEmitter { } // should update the status of the tx to 'submitted'. + // and add a time stamp for when it was called setTxStatusSubmitted (txId) { + const txMeta = this.getTx(txId) + txMeta.submittedTime = (new Date()).getTime() + this.updateTx(txMeta, 'txStateManager - add submitted time stamp') this._setTxStatus(txId, 'submitted') } @@ -211,6 +227,12 @@ module.exports = class TransactionStateManager extends EventEmitter { this._setTxStatus(txId, 'confirmed') } + // should update the status dropped + setTxStatusDropped (txId) { + this._setTxStatus(txId, 'dropped') + } + + setTxStatusFailed (txId, err) { const txMeta = this.getTx(txId) txMeta.err = { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 0a5c1d36f..18d71874a 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1,3 +1,9 @@ +/** + * @file The central metamask controller. Aggregates other controllers and exports an api. + * @copyright Copyright (c) 2018 MetaMask + * @license MIT + */ + const EventEmitter = require('events') const extend = require('xtend') const pump = require('pump') @@ -41,7 +47,11 @@ const seedPhraseVerifier = require('./lib/seed-phrase-verifier') module.exports = class MetamaskController extends EventEmitter { - constructor (opts) { + /** + * @constructor + * @param {Object} opts + */ + constructor (opts) { super() this.defaultMaxListeners = 20 @@ -223,10 +233,9 @@ module.exports = class MetamaskController extends EventEmitter { this.infuraController.store.subscribe(sendUpdate) } - // - // Constructor helpers - // - + /** + * Constructor helper: initialize a provider. + */ initializeProvider () { const providerOpts = { static: { @@ -257,6 +266,9 @@ module.exports = class MetamaskController extends EventEmitter { return providerProxy } + /** + * Constructor helper: initialize a public config store. + */ initPublicConfigStore () { // get init state const publicConfigStore = new ObservableStore() @@ -278,10 +290,15 @@ module.exports = class MetamaskController extends EventEmitter { return publicConfigStore } - // - // State Management - // +//============================================================================= +// EXPOSED TO THE UI SUBSYSTEM +//============================================================================= + /** + * The metamask-state of the various controllers, made available to the UI + * + * @returns {Object} status + */ getState () { const wallet = this.configManager.getWallet() const vault = this.keyringController.store.getState().vault @@ -316,10 +333,11 @@ module.exports = class MetamaskController extends EventEmitter { ) } - // - // Remote Features - // - + /** + * Returns an api-object which is consumed by the UI + * + * @returns {Object} + */ getApi () { const keyringController = this.keyringController const preferencesController = this.preferencesController @@ -400,127 +418,24 @@ module.exports = class MetamaskController extends EventEmitter { } } - setupUntrustedCommunication (connectionStream, originDomain) { - // Check if new connection is blacklisted - if (this.blacklistController.checkForPhishing(originDomain)) { - log.debug('MetaMask - sending phishing warning for', originDomain) - this.sendPhishingWarning(connectionStream, originDomain) - return - } - - // setup multiplexing - const mux = setupMultiplex(connectionStream) - // connect features - this.setupProviderConnection(mux.createStream('provider'), originDomain) - this.setupPublicConfig(mux.createStream('publicConfig')) - } - - setupTrustedCommunication (connectionStream, originDomain) { - // setup multiplexing - const mux = setupMultiplex(connectionStream) - // connect features - this.setupControllerConnection(mux.createStream('controller')) - this.setupProviderConnection(mux.createStream('provider'), originDomain) - } - - sendPhishingWarning (connectionStream, hostname) { - const mux = setupMultiplex(connectionStream) - const phishingStream = mux.createStream('phishing') - phishingStream.write({ hostname }) - } - - setupControllerConnection (outStream) { - const api = this.getApi() - const dnode = Dnode(api) - pump( - outStream, - dnode, - outStream, - (err) => { - if (err) log.error(err) - } - ) - dnode.on('remote', (remote) => { - // push updates to popup - const sendUpdate = remote.sendUpdate.bind(remote) - this.on('update', sendUpdate) - }) - } - - setupProviderConnection (outStream, origin) { - // setup json rpc engine stack - const engine = new RpcEngine() - - // create filter polyfill middleware - const filterMiddleware = createFilterMiddleware({ - provider: this.provider, - blockTracker: this.provider._blockTracker, - }) - - engine.push(createOriginMiddleware({ origin })) - engine.push(createLoggerMiddleware({ origin })) - engine.push(filterMiddleware) - engine.push(createProviderMiddleware({ provider: this.provider })) - - // setup connection - const providerStream = createEngineStream({ engine }) - pump( - outStream, - providerStream, - outStream, - (err) => { - // cleanup filter polyfill middleware - filterMiddleware.destroy() - if (err) log.error(err) - } - ) - } - - setupPublicConfig (outStream) { - pump( - asStream(this.publicConfigStore), - outStream, - (err) => { - if (err) log.error(err) - } - ) - } - privateSendUpdate () { - this.emit('update', this.getState()) - } - getGasPrice () { - const { recentBlocksController } = this - const { recentBlocks } = recentBlocksController.store.getState() - - // Return 1 gwei if no blocks have been observed: - if (recentBlocks.length === 0) { - return '0x' + GWEI_BN.toString(16) - } - - const lowestPrices = recentBlocks.map((block) => { - if (!block.gasPrices || block.gasPrices.length < 1) { - return GWEI_BN - } - return block.gasPrices - .map(hexPrefix => hexPrefix.substr(2)) - .map(hex => new BN(hex, 16)) - .sort((a, b) => { - return a.gt(b) ? 1 : -1 - })[0] - }) - .map(number => number.div(GWEI_BN).toNumber()) - - const percentileNum = percentile(50, lowestPrices) - const percentileNumBn = new BN(percentileNum) - return '0x' + percentileNumBn.mul(GWEI_BN).toString(16) - } - - // - // Vault Management - // +//============================================================================= +// VAULT / KEYRING RELATED METHODS +//============================================================================= + /** + * Creates a new Vault(?) and create a new keychain(?) + * + * A vault is ... + * + * A keychain is ... + * + * + * @param {} password + * + * @returns {} vault + */ async createNewVaultAndKeychain (password) { const release = await this.createVaultMutex.acquire() let vault @@ -544,6 +459,11 @@ module.exports = class MetamaskController extends EventEmitter { return vault } + /** + * Create a new Vault and restore an existent keychain + * @param {} password + * @param {} seed + */ async createNewVaultAndRestore (password, seed) { const release = await this.createVaultMutex.acquire() try { @@ -557,16 +477,28 @@ module.exports = class MetamaskController extends EventEmitter { } } + /** + * Retrieves the first Identiy from the passed Vault and selects the related address + * + * An Identity is ... + * + * @param {} vault + */ selectFirstIdentity (vault) { const { identities } = vault const address = Object.keys(identities)[0] this.preferencesController.setSelectedAddress(address) } - // + // ? // Opinionated Keyring Management // + /** + * Adds a new account to ... + * + * @returns {} keyState + */ async addNewAccount () { const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] if (!primaryKeyring) { @@ -588,10 +520,12 @@ module.exports = class MetamaskController extends EventEmitter { return keyState } - // Adds the current vault's seed words to the UI's state tree. - // - // Used when creating a first vault, to allow confirmation. - // Also used when revealing the seed words in the confirmation view. + /** + * Adds the current vault's seed words to the UI's state tree. + * + * Used when creating a first vault, to allow confirmation. + * Also used when revealing the seed words in the confirmation view. + */ placeSeedWords (cb) { this.verifySeedPhrase() @@ -604,10 +538,13 @@ module.exports = class MetamaskController extends EventEmitter { }) } - // Verifies the current vault's seed words if they can restore the - // accounts belonging to the current vault. - // - // Called when the first account is created and on unlocking the vault. + /** + * Verifies the validity of the current vault's seed phrase. + * + * Validity: seed phrase restores the accounts belonging to the current vault. + * + * Called when the first account is created and on unlocking the vault. + */ async verifySeedPhrase () { const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] @@ -632,22 +569,33 @@ module.exports = class MetamaskController extends EventEmitter { } } - // ClearSeedWordCache - // - // Removes the primary account's seed words from the UI's state tree, - // ensuring they are only ever available in the background process. + /** + * Remove the primary account seed phrase from the UI's state tree. + * + * The seed phrase remains available in the background process. + * + */ clearSeedWordCache (cb) { this.configManager.setSeedWords(null) cb(null, this.preferencesController.getSelectedAddress()) } - + + /** + * ? + */ resetAccount (cb) { const selectedAddress = this.preferencesController.getSelectedAddress() this.txController.wipeTransactions(selectedAddress) cb(null, selectedAddress) } - + /** + * Imports an account ... ? + * + * @param {} strategy + * @param {} args + * @param {} cb + */ importAccountWithStrategy (strategy, args, cb) { accountImporter.importAccount(strategy, args) .then((privateKey) => { @@ -659,11 +607,150 @@ module.exports = class MetamaskController extends EventEmitter { .catch((reason) => { cb(reason) }) } + // --------------------------------------------------------------------------- + // Identity Management (sign) - // - // Identity Management - // - // + /** + * @param {} msgParams + * @param {} cb + */ + signMessage (msgParams, cb) { + log.info('MetaMaskController - signMessage') + const msgId = msgParams.metamaskId + + // sets the status op the message to 'approved' + // and removes the metamaskId for signing + return this.messageManager.approveMessage(msgParams) + .then((cleanMsgParams) => { + // signs the message + return this.keyringController.signMessage(cleanMsgParams) + }) + .then((rawSig) => { + // tells the listener that the message has been signed + // and can be returned to the dapp + this.messageManager.setMsgStatusSigned(msgId, rawSig) + return this.getState() + }) + } + + // Prefixed Style Message Signing Methods: + + /** + * + * @param {} msgParams + * @param {} cb + */ + approvePersonalMessage (msgParams, cb) { + const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams) + this.sendUpdate() + this.opts.showUnconfirmedMessage() + this.personalMessageManager.once(`${msgId}:finished`, (data) => { + switch (data.status) { + case 'signed': + return cb(null, data.rawSig) + case 'rejected': + return cb(new Error('MetaMask Message Signature: User denied transaction signature.')) + default: + return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + } + }) + } + + /** + * @param {} msgParams + */ + signPersonalMessage (msgParams) { + log.info('MetaMaskController - signPersonalMessage') + const msgId = msgParams.metamaskId + // sets the status op the message to 'approved' + // and removes the metamaskId for signing + return this.personalMessageManager.approveMessage(msgParams) + .then((cleanMsgParams) => { + // signs the message + return this.keyringController.signPersonalMessage(cleanMsgParams) + }) + .then((rawSig) => { + // tells the listener that the message has been signed + // and can be returned to the dapp + this.personalMessageManager.setMsgStatusSigned(msgId, rawSig) + return this.getState() + }) + } + + /** + * @param {} msgParams + */ + signTypedMessage (msgParams) { + log.info('MetaMaskController - signTypedMessage') + const msgId = msgParams.metamaskId + // sets the status op the message to 'approved' + // and removes the metamaskId for signing + return this.typedMessageManager.approveMessage(msgParams) + .then((cleanMsgParams) => { + // signs the message + return this.keyringController.signTypedMessage(cleanMsgParams) + }) + .then((rawSig) => { + // tells the listener that the message has been signed + // and can be returned to the dapp + this.typedMessageManager.setMsgStatusSigned(msgId, rawSig) + return this.getState() + }) + } + + // --------------------------------------------------------------------------- + // Account Restauration + + /** + * ? + * + * @param {} migratorOutput + */ + restoreOldVaultAccounts (migratorOutput) { + const { serialized } = migratorOutput + return this.keyringController.restoreKeyring(serialized) + .then(() => migratorOutput) + } + + /** + * ? + * + * @param {} migratorOutput + */ + restoreOldLostAccounts (migratorOutput) { + const { lostAccounts } = migratorOutput + if (lostAccounts) { + this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address)) + return this.importLostAccounts(migratorOutput) + } + return Promise.resolve(migratorOutput) + } + + /** + * Import (lost) Accounts + * + * @param {Object} {lostAccounts} @Array accounts <{ address, privateKey }> + * + * Uses the array's private keys to create a new Simple Key Pair keychain + * and add it to the keyring controller. + */ + importLostAccounts ({ lostAccounts }) { + const privKeys = lostAccounts.map(acct => acct.privateKey) + return this.keyringController.restoreKeyring({ + type: 'Simple Key Pair', + data: privKeys, + }) + } + +//============================================================================= +// END (VAULT / KEYRING RELATED METHODS) +//============================================================================= + +// + +//============================================================================= +// MESSAGES +//============================================================================= async retryTransaction (txId, cb) { await this.txController.retryTransaction(txId) @@ -730,85 +817,13 @@ module.exports = class MetamaskController extends EventEmitter { }) } - signMessage (msgParams, cb) { - log.info('MetaMaskController - signMessage') - const msgId = msgParams.metamaskId - - // sets the status op the message to 'approved' - // and removes the metamaskId for signing - return this.messageManager.approveMessage(msgParams) - .then((cleanMsgParams) => { - // signs the message - return this.keyringController.signMessage(cleanMsgParams) - }) - .then((rawSig) => { - // tells the listener that the message has been signed - // and can be returned to the dapp - this.messageManager.setMsgStatusSigned(msgId, rawSig) - return this.getState() - }) - } - cancelMessage (msgId, cb) { const messageManager = this.messageManager messageManager.rejectMsg(msgId) if (cb && typeof cb === 'function') { cb(null, this.getState()) } - } - - // Prefixed Style Message Signing Methods: - approvePersonalMessage (msgParams, cb) { - const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams) - this.sendUpdate() - this.opts.showUnconfirmedMessage() - this.personalMessageManager.once(`${msgId}:finished`, (data) => { - switch (data.status) { - case 'signed': - return cb(null, data.rawSig) - case 'rejected': - return cb(new Error('MetaMask Message Signature: User denied transaction signature.')) - default: - return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) - } - }) - } - - signPersonalMessage (msgParams) { - log.info('MetaMaskController - signPersonalMessage') - const msgId = msgParams.metamaskId - // sets the status op the message to 'approved' - // and removes the metamaskId for signing - return this.personalMessageManager.approveMessage(msgParams) - .then((cleanMsgParams) => { - // signs the message - return this.keyringController.signPersonalMessage(cleanMsgParams) - }) - .then((rawSig) => { - // tells the listener that the message has been signed - // and can be returned to the dapp - this.personalMessageManager.setMsgStatusSigned(msgId, rawSig) - return this.getState() - }) - } - - signTypedMessage (msgParams) { - log.info('MetaMaskController - signTypedMessage') - const msgId = msgParams.metamaskId - // sets the status op the message to 'approved' - // and removes the metamaskId for signing - return this.typedMessageManager.approveMessage(msgParams) - .then((cleanMsgParams) => { - // signs the message - return this.keyringController.signTypedMessage(cleanMsgParams) - }) - .then((rawSig) => { - // tells the listener that the message has been signed - // and can be returned to the dapp - this.typedMessageManager.setMsgStatusSigned(msgId, rawSig) - return this.getState() - }) - } + } cancelPersonalMessage (msgId, cb) { const messageManager = this.personalMessageManager @@ -844,36 +859,130 @@ module.exports = class MetamaskController extends EventEmitter { cb() } - restoreOldVaultAccounts (migratorOutput) { - const { serialized } = migratorOutput - return this.keyringController.restoreKeyring(serialized) - .then(() => migratorOutput) - } +//============================================================================= +// SETUP +//============================================================================= - restoreOldLostAccounts (migratorOutput) { - const { lostAccounts } = migratorOutput - if (lostAccounts) { - this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address)) - return this.importLostAccounts(migratorOutput) + setupUntrustedCommunication (connectionStream, originDomain) { + // Check if new connection is blacklisted + if (this.blacklistController.checkForPhishing(originDomain)) { + log.debug('MetaMask - sending phishing warning for', originDomain) + this.sendPhishingWarning(connectionStream, originDomain) + return } - return Promise.resolve(migratorOutput) + + // setup multiplexing + const mux = setupMultiplex(connectionStream) + // connect features + this.setupProviderConnection(mux.createStream('provider'), originDomain) + this.setupPublicConfig(mux.createStream('publicConfig')) } - // IMPORT LOST ACCOUNTS - // @Object with key lostAccounts: @Array accounts <{ address, privateKey }> - // Uses the array's private keys to create a new Simple Key Pair keychain - // and add it to the keyring controller. - importLostAccounts ({ lostAccounts }) { - const privKeys = lostAccounts.map(acct => acct.privateKey) - return this.keyringController.restoreKeyring({ - type: 'Simple Key Pair', - data: privKeys, + setupTrustedCommunication (connectionStream, originDomain) { + // setup multiplexing + const mux = setupMultiplex(connectionStream) + // connect features + this.setupControllerConnection(mux.createStream('controller')) + this.setupProviderConnection(mux.createStream('provider'), originDomain) + } + + sendPhishingWarning (connectionStream, hostname) { + const mux = setupMultiplex(connectionStream) + const phishingStream = mux.createStream('phishing') + phishingStream.write({ hostname }) + } + + setupControllerConnection (outStream) { + const api = this.getApi() + const dnode = Dnode(api) + pump( + outStream, + dnode, + outStream, + (err) => { + if (err) log.error(err) + } + ) + dnode.on('remote', (remote) => { + // push updates to popup + const sendUpdate = remote.sendUpdate.bind(remote) + this.on('update', sendUpdate) }) } - // - // config - // + setupProviderConnection (outStream, origin) { + // setup json rpc engine stack + const engine = new RpcEngine() + + // create filter polyfill middleware + const filterMiddleware = createFilterMiddleware({ + provider: this.provider, + blockTracker: this.provider._blockTracker, + }) + + engine.push(createOriginMiddleware({ origin })) + engine.push(createLoggerMiddleware({ origin })) + engine.push(filterMiddleware) + engine.push(createProviderMiddleware({ provider: this.provider })) + + // setup connection + const providerStream = createEngineStream({ engine }) + pump( + outStream, + providerStream, + outStream, + (err) => { + // cleanup filter polyfill middleware + filterMiddleware.destroy() + if (err) log.error(err) + } + ) + } + + setupPublicConfig (outStream) { + pump( + asStream(this.publicConfigStore), + outStream, + (err) => { + if (err) log.error(err) + } + ) + } + + privateSendUpdate () { + this.emit('update', this.getState()) + } + + getGasPrice () { + const { recentBlocksController } = this + const { recentBlocks } = recentBlocksController.store.getState() + + // Return 1 gwei if no blocks have been observed: + if (recentBlocks.length === 0) { + return '0x' + GWEI_BN.toString(16) + } + + const lowestPrices = recentBlocks.map((block) => { + if (!block.gasPrices || block.gasPrices.length < 1) { + return GWEI_BN + } + return block.gasPrices + .map(hexPrefix => hexPrefix.substr(2)) + .map(hex => new BN(hex, 16)) + .sort((a, b) => { + return a.gt(b) ? 1 : -1 + })[0] + }) + .map(number => number.div(GWEI_BN).toNumber()) + + const percentileNum = percentile(50, lowestPrices) + const percentileNumBn = new BN(percentileNum) + return '0x' + percentileNumBn.mul(GWEI_BN).toString(16) + } + +//============================================================================= +// CONFIG +//============================================================================= // Log blocks diff --git a/app/scripts/migrations/README.md b/app/scripts/migrations/README.md new file mode 100644 index 000000000..3a67b08e1 --- /dev/null +++ b/app/scripts/migrations/README.md @@ -0,0 +1,5 @@ +# Migrations + +Data (user data, config files etc.) is migrated from one version to another. + +Migrations are called by {} from {} during {}.
\ No newline at end of file diff --git a/app/scripts/popup.js b/app/scripts/popup.js index 11d50ee87..e78981f06 100644 --- a/app/scripts/popup.js +++ b/app/scripts/popup.js @@ -65,6 +65,7 @@ startPopup({ container, connectionStream }, (err, store) => { function closePopupIfOpen (windowType) { if (windowType !== 'notification') { + // should close only chrome popup notificationManager.closePopup() } } diff --git a/app/scripts/vendor/raven.min.js b/app/scripts/vendor/raven.min.js deleted file mode 100644 index b439aeae6..000000000 --- a/app/scripts/vendor/raven.min.js +++ /dev/null @@ -1,3 +0,0 @@ -/*! Raven.js 3.22.1 (7584197) | github.com/getsentry/raven-js */ -!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.Raven=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){function d(a){this.name="RavenConfigError",this.message=a}d.prototype=new Error,d.prototype.constructor=d,b.exports=d},{}],2:[function(a,b,c){var d=function(a,b,c){var d=a[b],e=a;if(b in a){var f="warn"===b?"warning":b;a[b]=function(){var a=[].slice.call(arguments),g=""+a.join(" "),h={level:f,logger:"console",extra:{arguments:a}};"assert"===b?a[0]===!1&&(g="Assertion failed: "+(a.slice(1).join(" ")||"console.assert"),h.extra.arguments=a.slice(1),c&&c(g,h)):c&&c(g,h),d&&Function.prototype.apply.call(d,e,a)}}};b.exports={wrapMethod:d}},{}],3:[function(a,b,c){(function(c){function d(){return+new Date}function e(a,b){return o(b)?function(c){return b(c,a)}:b}function f(){this.a=!("object"!=typeof JSON||!JSON.stringify),this.b=!n(K),this.c=!n(L),this.d=null,this.e=null,this.f=null,this.g=null,this.h=null,this.i=null,this.j={},this.k={release:J.SENTRY_RELEASE&&J.SENTRY_RELEASE.id,logger:"javascript",ignoreErrors:[],ignoreUrls:[],whitelistUrls:[],includePaths:[],headers:null,collectWindowErrors:!0,maxMessageLength:0,maxUrlLength:250,stackTraceLimit:50,autoBreadcrumbs:!0,instrument:!0,sampleRate:1},this.l={method:"POST",keepalive:!0,referrerPolicy:"origin"},this.m=0,this.n=!1,this.o=Error.stackTraceLimit,this.p=J.console||{},this.q={},this.r=[],this.s=d(),this.t=[],this.u=[],this.v=null,this.w=J.location,this.x=this.w&&this.w.href,this.y();for(var a in this.p)this.q[a]=this.p[a]}var g=a(6),h=a(7),i=a(1),j=a(5),k=j.isError,l=j.isObject,m=j.isErrorEvent,n=j.isUndefined,o=j.isFunction,p=j.isString,q=j.isArray,r=j.isEmptyObject,s=j.each,t=j.objectMerge,u=j.truncate,v=j.objectFrozen,w=j.hasKey,x=j.joinRegExp,y=j.urlencode,z=j.uuid4,A=j.htmlTreeAsString,B=j.isSameException,C=j.isSameStacktrace,D=j.parseUrl,E=j.fill,F=j.supportsFetch,G=a(2).wrapMethod,H="source protocol user pass host port path".split(" "),I=/^(?:(\w+):)?\/\/(?:(\w+)(:\w+)?@)?([\w\.-]+)(?::(\d+))?(\/.*)/,J="undefined"!=typeof window?window:"undefined"!=typeof c?c:"undefined"!=typeof self?self:{},K=J.document,L=J.navigator;f.prototype={VERSION:"3.22.1",debug:!1,TraceKit:g,config:function(a,b){var c=this;if(c.g)return this.z("error","Error: Raven has already been configured"),c;if(!a)return c;var d=c.k;b&&s(b,function(a,b){"tags"===a||"extra"===a||"user"===a?c.j[a]=b:d[a]=b}),c.setDSN(a),d.ignoreErrors.push(/^Script error\.?$/),d.ignoreErrors.push(/^Javascript error: Script error\.? on line 0$/),d.ignoreErrors=x(d.ignoreErrors),d.ignoreUrls=!!d.ignoreUrls.length&&x(d.ignoreUrls),d.whitelistUrls=!!d.whitelistUrls.length&&x(d.whitelistUrls),d.includePaths=x(d.includePaths),d.maxBreadcrumbs=Math.max(0,Math.min(d.maxBreadcrumbs||100,100));var e={xhr:!0,console:!0,dom:!0,location:!0,sentry:!0},f=d.autoBreadcrumbs;"[object Object]"==={}.toString.call(f)?f=t(e,f):f!==!1&&(f=e),d.autoBreadcrumbs=f;var h={tryCatch:!0},i=d.instrument;return"[object Object]"==={}.toString.call(i)?i=t(h,i):i!==!1&&(i=h),d.instrument=i,g.collectWindowErrors=!!d.collectWindowErrors,c},install:function(){var a=this;return a.isSetup()&&!a.n&&(g.report.subscribe(function(){a.A.apply(a,arguments)}),a.B(),a.k.instrument&&a.k.instrument.tryCatch&&a.C(),a.k.autoBreadcrumbs&&a.D(),a.E(),a.n=!0),Error.stackTraceLimit=a.k.stackTraceLimit,this},setDSN:function(a){var b=this,c=b.F(a),d=c.path.lastIndexOf("/"),e=c.path.substr(1,d);b.G=a,b.h=c.user,b.H=c.pass&&c.pass.substr(1),b.i=c.path.substr(d+1),b.g=b.I(c),b.J=b.g+"/"+e+"api/"+b.i+"/store/",this.y()},context:function(a,b,c){return o(a)&&(c=b||[],b=a,a=void 0),this.wrap(a,b).apply(this,c)},wrap:function(a,b,c){function d(){var d=[],f=arguments.length,g=!a||a&&a.deep!==!1;for(c&&o(c)&&c.apply(this,arguments);f--;)d[f]=g?e.wrap(a,arguments[f]):arguments[f];try{return b.apply(this,d)}catch(h){throw e.K(),e.captureException(h,a),h}}var e=this;if(n(b)&&!o(a))return a;if(o(a)&&(b=a,a=void 0),!o(b))return b;try{if(b.L)return b;if(b.M)return b.M}catch(f){return b}for(var g in b)w(b,g)&&(d[g]=b[g]);return d.prototype=b.prototype,b.M=d,d.L=!0,d.N=b,d},uninstall:function(){return g.report.uninstall(),this.O(),this.P(),Error.stackTraceLimit=this.o,this.n=!1,this},captureException:function(a,b){var c=!k(a),d=!m(a),e=m(a)&&!a.error;if(c&&d||e)return this.captureMessage(a,t({trimHeadFrames:1,stacktrace:!0},b));m(a)&&(a=a.error),this.d=a;try{var f=g.computeStackTrace(a);this.Q(f,b)}catch(h){if(a!==h)throw h}return this},captureMessage:function(a,b){if(!this.k.ignoreErrors.test||!this.k.ignoreErrors.test(a)){b=b||{};var c,d=t({message:a+""},b);try{throw new Error(a)}catch(e){c=e}c.name=null;var f=g.computeStackTrace(c),h=q(f.stack)&&f.stack[1],i=h&&h.url||"";if((!this.k.ignoreUrls.test||!this.k.ignoreUrls.test(i))&&(!this.k.whitelistUrls.test||this.k.whitelistUrls.test(i))){if(this.k.stacktrace||b&&b.stacktrace){b=t({fingerprint:a,trimHeadFrames:(b.trimHeadFrames||0)+1},b);var j=this.R(f,b);d.stacktrace={frames:j.reverse()}}return this.S(d),this}}},captureBreadcrumb:function(a){var b=t({timestamp:d()/1e3},a);if(o(this.k.breadcrumbCallback)){var c=this.k.breadcrumbCallback(b);if(l(c)&&!r(c))b=c;else if(c===!1)return this}return this.u.push(b),this.u.length>this.k.maxBreadcrumbs&&this.u.shift(),this},addPlugin:function(a){var b=[].slice.call(arguments,1);return this.r.push([a,b]),this.n&&this.E(),this},setUserContext:function(a){return this.j.user=a,this},setExtraContext:function(a){return this.T("extra",a),this},setTagsContext:function(a){return this.T("tags",a),this},clearContext:function(){return this.j={},this},getContext:function(){return JSON.parse(h(this.j))},setEnvironment:function(a){return this.k.environment=a,this},setRelease:function(a){return this.k.release=a,this},setDataCallback:function(a){var b=this.k.dataCallback;return this.k.dataCallback=e(b,a),this},setBreadcrumbCallback:function(a){var b=this.k.breadcrumbCallback;return this.k.breadcrumbCallback=e(b,a),this},setShouldSendCallback:function(a){var b=this.k.shouldSendCallback;return this.k.shouldSendCallback=e(b,a),this},setTransport:function(a){return this.k.transport=a,this},lastException:function(){return this.d},lastEventId:function(){return this.f},isSetup:function(){return!!this.a&&(!!this.g||(this.ravenNotConfiguredError||(this.ravenNotConfiguredError=!0,this.z("error","Error: Raven has not been configured.")),!1))},afterLoad:function(){var a=J.RavenConfig;a&&this.config(a.dsn,a.config).install()},showReportDialog:function(a){if(K){a=a||{};var b=a.eventId||this.lastEventId();if(!b)throw new i("Missing eventId");var c=a.dsn||this.G;if(!c)throw new i("Missing DSN");var d=encodeURIComponent,e="";e+="?eventId="+d(b),e+="&dsn="+d(c);var f=a.user||this.j.user;f&&(f.name&&(e+="&name="+d(f.name)),f.email&&(e+="&email="+d(f.email)));var g=this.I(this.F(c)),h=K.createElement("script");h.async=!0,h.src=g+"/api/embed/error-page/"+e,(K.head||K.body).appendChild(h)}},K:function(){var a=this;this.m+=1,setTimeout(function(){a.m-=1})},U:function(a,b){var c,d;if(this.b){b=b||{},a="raven"+a.substr(0,1).toUpperCase()+a.substr(1),K.createEvent?(c=K.createEvent("HTMLEvents"),c.initEvent(a,!0,!0)):(c=K.createEventObject(),c.eventType=a);for(d in b)w(b,d)&&(c[d]=b[d]);if(K.createEvent)K.dispatchEvent(c);else try{K.fireEvent("on"+c.eventType.toLowerCase(),c)}catch(e){}}},V:function(a){var b=this;return function(c){if(b.W=null,b.v!==c){b.v=c;var d;try{d=A(c.target)}catch(e){d="<unknown>"}b.captureBreadcrumb({category:"ui."+a,message:d})}}},X:function(){var a=this,b=1e3;return function(c){var d;try{d=c.target}catch(e){return}var f=d&&d.tagName;if(f&&("INPUT"===f||"TEXTAREA"===f||d.isContentEditable)){var g=a.W;g||a.V("input")(c),clearTimeout(g),a.W=setTimeout(function(){a.W=null},b)}}},Y:function(a,b){var c=D(this.w.href),d=D(b),e=D(a);this.x=b,c.protocol===d.protocol&&c.host===d.host&&(b=d.relative),c.protocol===e.protocol&&c.host===e.host&&(a=e.relative),this.captureBreadcrumb({category:"navigation",data:{to:b,from:a}})},B:function(){var a=this;a.Z=Function.prototype.toString,Function.prototype.toString=function(){return"function"==typeof this&&this.L?a.Z.apply(this.N,arguments):a.Z.apply(this,arguments)}},O:function(){this.Z&&(Function.prototype.toString=this.Z)},C:function(){function a(a){return function(b,d){for(var e=new Array(arguments.length),f=0;f<e.length;++f)e[f]=arguments[f];var g=e[0];return o(g)&&(e[0]=c.wrap(g)),a.apply?a.apply(this,e):a(e[0],e[1])}}function b(a){var b=J[a]&&J[a].prototype;b&&b.hasOwnProperty&&b.hasOwnProperty("addEventListener")&&(E(b,"addEventListener",function(b){return function(d,f,g,h){try{f&&f.handleEvent&&(f.handleEvent=c.wrap(f.handleEvent))}catch(i){}var j,k,l;return e&&e.dom&&("EventTarget"===a||"Node"===a)&&(k=c.V("click"),l=c.X(),j=function(a){if(a){var b;try{b=a.type}catch(c){return}return"click"===b?k(a):"keypress"===b?l(a):void 0}}),b.call(this,d,c.wrap(f,void 0,j),g,h)}},d),E(b,"removeEventListener",function(a){return function(b,c,d,e){try{c=c&&(c.M?c.M:c)}catch(f){}return a.call(this,b,c,d,e)}},d))}var c=this,d=c.t,e=this.k.autoBreadcrumbs;E(J,"setTimeout",a,d),E(J,"setInterval",a,d),J.requestAnimationFrame&&E(J,"requestAnimationFrame",function(a){return function(b){return a(c.wrap(b))}},d);for(var f=["EventTarget","Window","Node","ApplicationCache","AudioTrackList","ChannelMergerNode","CryptoOperation","EventSource","FileReader","HTMLUnknownElement","IDBDatabase","IDBRequest","IDBTransaction","KeyOperation","MediaController","MessagePort","ModalWindow","Notification","SVGElementInstance","Screen","TextTrack","TextTrackCue","TextTrackList","WebSocket","WebSocketWorker","Worker","XMLHttpRequest","XMLHttpRequestEventTarget","XMLHttpRequestUpload"],g=0;g<f.length;g++)b(f[g])},D:function(){function a(a,c){a in c&&o(c[a])&&E(c,a,function(a){return b.wrap(a)})}var b=this,c=this.k.autoBreadcrumbs,d=b.t;if(c.xhr&&"XMLHttpRequest"in J){var e=XMLHttpRequest.prototype;E(e,"open",function(a){return function(c,d){return p(d)&&d.indexOf(b.h)===-1&&(this.$={method:c,url:d,status_code:null}),a.apply(this,arguments)}},d),E(e,"send",function(c){return function(){function d(){if(e.$&&4===e.readyState){try{e.$.status_code=e.status}catch(a){}b.captureBreadcrumb({type:"http",category:"xhr",data:e.$})}}for(var e=this,f=["onload","onerror","onprogress"],g=0;g<f.length;g++)a(f[g],e);return"onreadystatechange"in e&&o(e.onreadystatechange)?E(e,"onreadystatechange",function(a){return b.wrap(a,void 0,d)}):e.onreadystatechange=d,c.apply(this,arguments)}},d)}c.xhr&&F()&&E(J,"fetch",function(a){return function(){for(var c=new Array(arguments.length),d=0;d<c.length;++d)c[d]=arguments[d];var e,f=c[0],g="GET";if("string"==typeof f?e=f:"Request"in J&&f instanceof J.Request?(e=f.url,f.method&&(g=f.method)):e=""+f,e.indexOf(b.h)!==-1)return a.apply(this,c);c[1]&&c[1].method&&(g=c[1].method);var h={method:g,url:e,status_code:null};return a.apply(this,c).then(function(a){return h.status_code=a.status,b.captureBreadcrumb({type:"http",category:"fetch",data:h}),a})}},d),c.dom&&this.b&&(K.addEventListener?(K.addEventListener("click",b.V("click"),!1),K.addEventListener("keypress",b.X(),!1)):(K.attachEvent("onclick",b.V("click")),K.attachEvent("onkeypress",b.X())));var f=J.chrome,g=f&&f.app&&f.app.runtime,h=!g&&J.history&&history.pushState&&history.replaceState;if(c.location&&h){var i=J.onpopstate;J.onpopstate=function(){var a=b.w.href;if(b.Y(b.x,a),i)return i.apply(this,arguments)};var j=function(a){return function(){var c=arguments.length>2?arguments[2]:void 0;return c&&b.Y(b.x,c+""),a.apply(this,arguments)}};E(history,"pushState",j,d),E(history,"replaceState",j,d)}if(c.console&&"console"in J&&console.log){var k=function(a,c){b.captureBreadcrumb({message:a,level:c.level,category:"console"})};s(["debug","info","warn","error","log"],function(a,b){G(console,b,k)})}},P:function(){for(var a;this.t.length;){a=this.t.shift();var b=a[0],c=a[1],d=a[2];b[c]=d}},E:function(){var a=this;s(this.r,function(b,c){var d=c[0],e=c[1];d.apply(a,[a].concat(e))})},F:function(a){var b=I.exec(a),c={},d=7;try{for(;d--;)c[H[d]]=b[d]||""}catch(e){throw new i("Invalid DSN: "+a)}if(c.pass&&!this.k.allowSecretKey)throw new i("Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key");return c},I:function(a){var b="//"+a.host+(a.port?":"+a.port:"");return a.protocol&&(b=a.protocol+":"+b),b},A:function(){this.m||this.Q.apply(this,arguments)},Q:function(a,b){var c=this.R(a,b);this.U("handle",{stackInfo:a,options:b}),this._(a.name,a.message,a.url,a.lineno,c,b)},R:function(a,b){var c=this,d=[];if(a.stack&&a.stack.length&&(s(a.stack,function(b,e){var f=c.aa(e,a.url);f&&d.push(f)}),b&&b.trimHeadFrames))for(var e=0;e<b.trimHeadFrames&&e<d.length;e++)d[e].in_app=!1;return d=d.slice(0,this.k.stackTraceLimit)},aa:function(a,b){var c={filename:a.url,lineno:a.line,colno:a.column,"function":a.func||"?"};return a.url||(c.filename=b),c.in_app=!(this.k.includePaths.test&&!this.k.includePaths.test(c.filename)||/(Raven|TraceKit)\./.test(c["function"])||/raven\.(min\.)?js$/.test(c.filename)),c},_:function(a,b,c,d,e,f){var g=(a?a+": ":"")+(b||"");if(!this.k.ignoreErrors.test||!this.k.ignoreErrors.test(b)&&!this.k.ignoreErrors.test(g)){var h;if(e&&e.length?(c=e[0].filename||c,e.reverse(),h={frames:e}):c&&(h={frames:[{filename:c,lineno:d,in_app:!0}]}),(!this.k.ignoreUrls.test||!this.k.ignoreUrls.test(c))&&(!this.k.whitelistUrls.test||this.k.whitelistUrls.test(c))){var i=t({exception:{values:[{type:a,value:b,stacktrace:h}]},culprit:c},f);this.S(i)}}},ba:function(a){var b=this.k.maxMessageLength;if(a.message&&(a.message=u(a.message,b)),a.exception){var c=a.exception.values[0];c.value=u(c.value,b)}var d=a.request;return d&&(d.url&&(d.url=u(d.url,this.k.maxUrlLength)),d.Referer&&(d.Referer=u(d.Referer,this.k.maxUrlLength))),a.breadcrumbs&&a.breadcrumbs.values&&this.ca(a.breadcrumbs),a},ca:function(a){for(var b,c,d,e=["to","from","url"],f=0;f<a.values.length;++f)if(c=a.values[f],c.hasOwnProperty("data")&&l(c.data)&&!v(c.data)){d=t({},c.data);for(var g=0;g<e.length;++g)b=e[g],d.hasOwnProperty(b)&&d[b]&&(d[b]=u(d[b],this.k.maxUrlLength));a.values[f].data=d}},da:function(){if(this.c||this.b){var a={};return this.c&&L.userAgent&&(a.headers={"User-Agent":navigator.userAgent}),J.location&&J.location.href&&(a.url=J.location.href),this.b&&K.referrer&&(a.headers||(a.headers={}),a.headers.Referer=K.referrer),a}},y:function(){this.ea=0,this.fa=null},ga:function(){return this.ea&&d()-this.fa<this.ea},ha:function(a){var b=this.e;return!(!b||a.message!==b.message||a.culprit!==b.culprit)&&(a.stacktrace||b.stacktrace?C(a.stacktrace,b.stacktrace):!a.exception&&!b.exception||B(a.exception,b.exception))},ia:function(a){if(!this.ga()){var b=a.status;if(400===b||401===b||429===b){var c;try{c=F()?a.headers.get("Retry-After"):a.getResponseHeader("Retry-After"),c=1e3*parseInt(c,10)}catch(e){}this.ea=c?c:2*this.ea||1e3,this.fa=d()}}},S:function(a){var b=this.k,c={project:this.i,logger:b.logger,platform:"javascript"},e=this.da();if(e&&(c.request=e),a.trimHeadFrames&&delete a.trimHeadFrames,a=t(c,a),a.tags=t(t({},this.j.tags),a.tags),a.extra=t(t({},this.j.extra),a.extra),a.extra["session:duration"]=d()-this.s,this.u&&this.u.length>0&&(a.breadcrumbs={values:[].slice.call(this.u,0)}),this.j.user&&(a.user=this.j.user),b.environment&&(a.environment=b.environment),b.release&&(a.release=b.release),b.serverName&&(a.server_name=b.serverName),Object.keys(a).forEach(function(b){(null==a[b]||""===a[b]||r(a[b]))&&delete a[b]}),o(b.dataCallback)&&(a=b.dataCallback(a)||a),a&&!r(a)&&(!o(b.shouldSendCallback)||b.shouldSendCallback(a)))return this.ga()?void this.z("warn","Raven dropped error due to backoff: ",a):void("number"==typeof b.sampleRate?Math.random()<b.sampleRate&&this.ja(a):this.ja(a))},ka:function(){return z()},ja:function(a,b){var c=this,d=this.k;if(this.isSetup()){if(a=this.ba(a),!this.k.allowDuplicates&&this.ha(a))return void this.z("warn","Raven dropped repeat event: ",a);this.f=a.event_id||(a.event_id=this.ka()),this.e=a,this.z("debug","Raven about to send:",a);var e={sentry_version:"7",sentry_client:"raven-js/"+this.VERSION,sentry_key:this.h};this.H&&(e.sentry_secret=this.H);var f=a.exception&&a.exception.values[0];this.k.autoBreadcrumbs&&this.k.autoBreadcrumbs.sentry&&this.captureBreadcrumb({category:"sentry",message:f?(f.type?f.type+": ":"")+f.value:a.message,event_id:a.event_id,level:a.level||"error"});var g=this.J;(d.transport||this.la).call(this,{url:g,auth:e,data:a,options:d,onSuccess:function(){c.y(),c.U("success",{data:a,src:g}),b&&b()},onError:function(d){c.z("error","Raven transport failed to send: ",d),d.request&&c.ia(d.request),c.U("failure",{data:a,src:g}),d=d||new Error("Raven send failed (no additional details provided)"),b&&b(d)}})}},la:function(a){var b=a.url+"?"+y(a.auth),c=null,d={};if(a.options.headers&&(c=this.ma(a.options.headers)),a.options.fetchParameters&&(d=this.ma(a.options.fetchParameters)),F()){d.body=h(a.data);var e=t({},this.l),f=t(e,d);return c&&(f.headers=c),J.fetch(b,f).then(function(b){if(b.ok)a.onSuccess&&a.onSuccess();else{var c=new Error("Sentry error code: "+b.status);c.request=b,a.onError&&a.onError(c)}})["catch"](function(){a.onError&&a.onError(new Error("Sentry error code: network unavailable"))})}var g=J.XMLHttpRequest&&new J.XMLHttpRequest;if(g){var i="withCredentials"in g||"undefined"!=typeof XDomainRequest;i&&("withCredentials"in g?g.onreadystatechange=function(){if(4===g.readyState)if(200===g.status)a.onSuccess&&a.onSuccess();else if(a.onError){var b=new Error("Sentry error code: "+g.status);b.request=g,a.onError(b)}}:(g=new XDomainRequest,b=b.replace(/^https?:/,""),a.onSuccess&&(g.onload=a.onSuccess),a.onError&&(g.onerror=function(){var b=new Error("Sentry error code: XDomainRequest");b.request=g,a.onError(b)})),g.open("POST",b),c&&s(c,function(a,b){g.setRequestHeader(a,b)}),g.send(h(a.data)))}},ma:function(a){var b={};for(var c in a)if(a.hasOwnProperty(c)){var d=a[c];b[c]="function"==typeof d?d():d}return b},z:function(a){this.q[a]&&this.debug&&Function.prototype.apply.call(this.q[a],this.p,[].slice.call(arguments,1))},T:function(a,b){n(b)?delete this.j[a]:this.j[a]=t(this.j[a]||{},b)}},f.prototype.setUser=f.prototype.setUserContext,f.prototype.setReleaseContext=f.prototype.setRelease,b.exports=f}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{1:1,2:2,5:5,6:6,7:7}],4:[function(a,b,c){(function(c){var d=a(3),e="undefined"!=typeof window?window:"undefined"!=typeof c?c:"undefined"!=typeof self?self:{},f=e.Raven,g=new d;g.noConflict=function(){return e.Raven=f,g},g.afterLoad(),b.exports=g}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{3:3}],5:[function(a,b,c){(function(a){function c(a){return"object"==typeof a&&null!==a}function d(a){switch({}.toString.call(a)){case"[object Error]":return!0;case"[object Exception]":return!0;case"[object DOMException]":return!0;default:return a instanceof Error}}function e(a){return l()&&"[object ErrorEvent]"==={}.toString.call(a)}function f(a){return void 0===a}function g(a){return"function"==typeof a}function h(a){return"[object Object]"===Object.prototype.toString.call(a)}function i(a){return"[object String]"===Object.prototype.toString.call(a)}function j(a){return"[object Array]"===Object.prototype.toString.call(a)}function k(a){if(!h(a))return!1;for(var b in a)if(a.hasOwnProperty(b))return!1;return!0}function l(){try{return new ErrorEvent(""),!0}catch(a){return!1}}function m(){if(!("fetch"in E))return!1;try{return new Headers,new Request(""),new Response,!0}catch(a){return!1}}function n(a){function b(b,c){var d=a(b)||b;return c?c(d)||d:d}return b}function o(a,b){var c,d;if(f(a.length))for(c in a)s(a,c)&&b.call(null,c,a[c]);else if(d=a.length)for(c=0;c<d;c++)b.call(null,c,a[c])}function p(a,b){return b?(o(b,function(b,c){a[b]=c}),a):a}function q(a){return!!Object.isFrozen&&Object.isFrozen(a)}function r(a,b){return!b||a.length<=b?a:a.substr(0,b)+"…"}function s(a,b){return Object.prototype.hasOwnProperty.call(a,b)}function t(a){for(var b,c=[],d=0,e=a.length;d<e;d++)b=a[d],i(b)?c.push(b.replace(/([.*+?^=!:${}()|\[\]\/\\])/g,"\\$1")):b&&b.source&&c.push(b.source);return new RegExp(c.join("|"),"i")}function u(a){var b=[];return o(a,function(a,c){b.push(encodeURIComponent(a)+"="+encodeURIComponent(c))}),b.join("&")}function v(a){if("string"!=typeof a)return{};var b=a.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/),c=b[6]||"",d=b[8]||"";return{protocol:b[2],host:b[4],path:b[5],relative:b[5]+c+d}}function w(){var a=E.crypto||E.msCrypto;if(!f(a)&&a.getRandomValues){var b=new Uint16Array(8);a.getRandomValues(b),b[3]=4095&b[3]|16384,b[4]=16383&b[4]|32768;var c=function(a){for(var b=a.toString(16);b.length<4;)b="0"+b;return b};return c(b[0])+c(b[1])+c(b[2])+c(b[3])+c(b[4])+c(b[5])+c(b[6])+c(b[7])}return"xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g,function(a){var b=16*Math.random()|0,c="x"===a?b:3&b|8;return c.toString(16)})}function x(a){for(var b,c=5,d=80,e=[],f=0,g=0,h=" > ",i=h.length;a&&f++<c&&(b=y(a),!("html"===b||f>1&&g+e.length*i+b.length>=d));)e.push(b),g+=b.length,a=a.parentNode;return e.reverse().join(h)}function y(a){var b,c,d,e,f,g=[];if(!a||!a.tagName)return"";if(g.push(a.tagName.toLowerCase()),a.id&&g.push("#"+a.id),b=a.className,b&&i(b))for(c=b.split(/\s+/),f=0;f<c.length;f++)g.push("."+c[f]);var h=["type","name","title","alt"];for(f=0;f<h.length;f++)d=h[f],e=a.getAttribute(d),e&&g.push("["+d+'="'+e+'"]');return g.join("")}function z(a,b){return!!(!!a^!!b)}function A(a,b){return f(a)&&f(b)}function B(a,b){return!z(a,b)&&(a=a.values[0],b=b.values[0],a.type===b.type&&a.value===b.value&&(!A(a.stacktrace,b.stacktrace)&&C(a.stacktrace,b.stacktrace)))}function C(a,b){if(z(a,b))return!1;var c=a.frames,d=b.frames;if(c.length!==d.length)return!1;for(var e,f,g=0;g<c.length;g++)if(e=c[g],f=d[g],e.filename!==f.filename||e.lineno!==f.lineno||e.colno!==f.colno||e["function"]!==f["function"])return!1;return!0}function D(a,b,c,d){var e=a[b];a[b]=c(e),a[b].L=!0,a[b].N=e,d&&d.push([a,b,e])}var E="undefined"!=typeof window?window:"undefined"!=typeof a?a:"undefined"!=typeof self?self:{};b.exports={isObject:c,isError:d,isErrorEvent:e,isUndefined:f,isFunction:g,isPlainObject:h,isString:i,isArray:j,isEmptyObject:k,supportsErrorEvent:l,supportsFetch:m,wrappedCallback:n,each:o,objectMerge:p,truncate:r,objectFrozen:q,hasKey:s,joinRegExp:t,urlencode:u,uuid4:w,htmlTreeAsString:x,htmlElementAsString:y,isSameException:B,isSameStacktrace:C,parseUrl:v,fill:D}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],6:[function(a,b,c){(function(c){function d(){return"undefined"==typeof document||null==document.location?"":document.location.href}var e=a(5),f={collectWindowErrors:!0,debug:!1},g="undefined"!=typeof window?window:"undefined"!=typeof c?c:"undefined"!=typeof self?self:{},h=[].slice,i="?",j=/^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/;f.report=function(){function a(a){m(),s.push(a)}function b(a){for(var b=s.length-1;b>=0;--b)s[b]===a&&s.splice(b,1)}function c(){n(),s=[]}function k(a,b){var c=null;if(!b||f.collectWindowErrors){for(var d in s)if(s.hasOwnProperty(d))try{s[d].apply(null,[a].concat(h.call(arguments,2)))}catch(e){c=e}if(c)throw c}}function l(a,b,c,g,h){var l=null,m=e.isErrorEvent(h)?h.error:h,n=e.isErrorEvent(a)?a.message:a;if(v)f.computeStackTrace.augmentStackTraceWithInitialElement(v,b,c,n),o();else if(m&&e.isError(m))l=f.computeStackTrace(m),k(l,!0);else{var p,r={url:b,line:c,column:g},s=void 0;if("[object String]"==={}.toString.call(n)){var p=n.match(j);p&&(s=p[1],n=p[2])}r.func=i,l={name:s,message:n,url:d(),stack:[r]},k(l,!0)}return!!q&&q.apply(this,arguments)}function m(){r||(q=g.onerror,g.onerror=l,r=!0)}function n(){r&&(g.onerror=q,r=!1,q=void 0)}function o(){var a=v,b=t;t=null,v=null,u=null,k.apply(null,[a,!1].concat(b))}function p(a,b){var c=h.call(arguments,1);if(v){if(u===a)return;o()}var d=f.computeStackTrace(a);if(v=d,u=a,t=c,setTimeout(function(){u===a&&o()},d.incomplete?2e3:0),b!==!1)throw a}var q,r,s=[],t=null,u=null,v=null;return p.subscribe=a,p.unsubscribe=b,p.uninstall=c,p}(),f.computeStackTrace=function(){function a(a){if("undefined"!=typeof a.stack&&a.stack){for(var b,c,e,f=/^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack|<anonymous>|[a-z]:|\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,g=/^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\[native).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$/i,h=/^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx(?:-web)|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i,j=/(\S+) line (\d+)(?: > eval line \d+)* > eval/i,k=/\((\S*)(?::(\d+))(?::(\d+))\)/,l=a.stack.split("\n"),m=[],n=(/^(.*) is undefined$/.exec(a.message),0),o=l.length;n<o;++n){if(c=f.exec(l[n])){var p=c[2]&&0===c[2].indexOf("native"),q=c[2]&&0===c[2].indexOf("eval");q&&(b=k.exec(c[2]))&&(c[2]=b[1],c[3]=b[2],c[4]=b[3]),e={url:p?null:c[2],func:c[1]||i,args:p?[c[2]]:[],line:c[3]?+c[3]:null,column:c[4]?+c[4]:null}}else if(c=h.exec(l[n]))e={url:c[2],func:c[1]||i,args:[],line:+c[3],column:c[4]?+c[4]:null};else{if(!(c=g.exec(l[n])))continue;var q=c[3]&&c[3].indexOf(" > eval")>-1;q&&(b=j.exec(c[3]))?(c[3]=b[1],c[4]=b[2],c[5]=null):0!==n||c[5]||"undefined"==typeof a.columnNumber||(m[0].column=a.columnNumber+1),e={url:c[3],func:c[1]||i,args:c[2]?c[2].split(","):[],line:c[4]?+c[4]:null,column:c[5]?+c[5]:null}}!e.func&&e.line&&(e.func=i),m.push(e)}return m.length?{name:a.name,message:a.message,url:d(),stack:m}:null}}function b(a,b,c,d){var e={url:b,line:c};if(e.url&&e.line){if(a.incomplete=!1,e.func||(e.func=i),a.stack.length>0&&a.stack[0].url===e.url){if(a.stack[0].line===e.line)return!1;if(!a.stack[0].line&&a.stack[0].func===e.func)return a.stack[0].line=e.line,!1}return a.stack.unshift(e),a.partial=!0,!0}return a.incomplete=!0,!1}function c(a,g){for(var h,j,k=/function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i,l=[],m={},n=!1,o=c.caller;o&&!n;o=o.caller)if(o!==e&&o!==f.report){if(j={url:null,func:i,line:null,column:null},o.name?j.func=o.name:(h=k.exec(o.toString()))&&(j.func=h[1]),"undefined"==typeof j.func)try{j.func=h.input.substring(0,h.input.indexOf("{"))}catch(p){}m[""+o]?n=!0:m[""+o]=!0,l.push(j)}g&&l.splice(0,g);var q={name:a.name,message:a.message,url:d(),stack:l};return b(q,a.sourceURL||a.fileName,a.line||a.lineNumber,a.message||a.description),q}function e(b,e){var g=null;e=null==e?0:+e;try{if(g=a(b))return g}catch(h){if(f.debug)throw h}try{if(g=c(b,e+1))return g}catch(h){if(f.debug)throw h}return{name:b.name,message:b.message,url:d()}}return e.augmentStackTraceWithInitialElement=b,e.computeStackTraceFromStackProp=a,e}(),b.exports=f}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{5:5}],7:[function(a,b,c){function d(a,b){for(var c=0;c<a.length;++c)if(a[c]===b)return c;return-1}function e(a,b,c,d){return JSON.stringify(a,g(b,d),c)}function f(a){var b={stack:a.stack,message:a.message,name:a.name};for(var c in a)Object.prototype.hasOwnProperty.call(a,c)&&(b[c]=a[c]);return b}function g(a,b){var c=[],e=[];return null==b&&(b=function(a,b){return c[0]===b?"[Circular ~]":"[Circular ~."+e.slice(0,d(c,b)).join(".")+"]"}),function(g,h){if(c.length>0){var i=d(c,this);~i?c.splice(i+1):c.push(this),~i?e.splice(i,1/0,g):e.push(g),~d(c,h)&&(h=b.call(this,g,h))}else c.push(h);return null==a?h instanceof Error?f(h):h:a.call(this,g,h)}}c=b.exports=e,c.getSerialize=g},{}]},{},[4])(4)}); -//# sourceMappingURL=raven.min.js.map
\ No newline at end of file diff --git a/development/README.md b/development/README.md new file mode 100644 index 000000000..1e18d4f16 --- /dev/null +++ b/development/README.md @@ -0,0 +1,5 @@ +# Development + +Several files which are needed for developing on(!) MetaMask. + +Usually each files contains information about its scope / usage.
\ No newline at end of file diff --git a/development/states/confirm-new-ui.json b/development/states/confirm-new-ui.json index 6ea8e64cd..6981781a9 100644 --- a/development/states/confirm-new-ui.json +++ b/development/states/confirm-new-ui.json @@ -116,7 +116,7 @@ "send": { "gasLimit": "0xea60", "gasPrice": "0xba43b7400", - "gasTotal": "0xb451dc41b578", + "gasTotal": "0xaa87bee538000", "tokenBalance": null, "from": { "address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", diff --git a/development/states/send-edit.json b/development/states/send-edit.json index 6ea8e64cd..6981781a9 100644 --- a/development/states/send-edit.json +++ b/development/states/send-edit.json @@ -116,7 +116,7 @@ "send": { "gasLimit": "0xea60", "gasPrice": "0xba43b7400", - "gasTotal": "0xb451dc41b578", + "gasTotal": "0xaa87bee538000", "tokenBalance": null, "from": { "address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", diff --git a/development/verify-locale-strings.js b/development/verify-locale-strings.js new file mode 100644 index 000000000..b8fe5a7dc --- /dev/null +++ b/development/verify-locale-strings.js @@ -0,0 +1,96 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Locale verification script +// +// usage: +// +// node app/scripts/verify-locale-strings.js <locale> +// +// will check the given locale against the strings in english +// +//////////////////////////////////////////////////////////////////////////////// + +var fs = require('fs') +var path = require('path') + +console.log('Locale Verification') + +var locale = process.argv[2] +if (!locale || locale == '') { + console.log('Must enter a locale as argument. exitting') + process.exit(1) +} + +console.log("verifying for locale " + locale) + +localeFilePath = path.join(process.cwd(), 'app', '_locales', locale, 'messages.json') +try { + localeObj = JSON.parse(fs.readFileSync(localeFilePath, 'utf8')); +} catch (e) { + if(e.code == 'ENOENT') { + console.log('Locale file not found') + } else { + console.log('Error opening your locale file: ', e) + } + process.exit(1) +} + +englishFilePath = path.join(process.cwd(), 'app', '_locales', 'en', 'messages.json') +try { + englishObj = JSON.parse(fs.readFileSync(englishFilePath, 'utf8')); +} catch (e) { + if(e.code == 'ENOENT') { + console.log("English File not found") + } else { + console.log("Error opening english locale file: ", e) + } + process.exit(1) +} + +console.log('\tverifying whether all your locale strings are contained in the english one') + +var counter = 0 +var foundErrorA = false +var notFound = []; +Object.keys(localeObj).forEach(function(key){ + if (!englishObj[key]) { + foundErrorA = true + notFound.push(key) + } + counter++ +}) + +if (foundErrorA) { + console.log('\nThe following string(s) is(are) not found in the english locale:') + notFound.forEach(function(key) { + console.log(key) + }) +} else { + console.log('\tall ' + counter +' strings declared in your locale were found in the english one') +} + +console.log('\n\tverifying whether your locale contains all english strings') + +var counter = 0 +var foundErrorB = false +var notFound = []; +Object.keys(englishObj).forEach(function(key){ + if (!localeObj[key]) { + foundErrorB = true + notFound.push(key) + } + counter++ +}) + +if (foundErrorB) { + console.log('\nThe following string(s) is(are) not found in the your locale:') + notFound.forEach(function(key) { + console.log(key) + }) +} else { + console.log('\tall ' + counter +' english strings were found in your locale!') +} + +if (!foundErrorA && !foundErrorB) { + console.log('You are good to go') +}
\ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..0739cfa46 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,13 @@ +# Documentation + + +- [How to add custom build to Chrome](./add-to-chrome.md) +- [How to add custom build to Firefox](./add-to-firefox.md) +- [How to develop a live-reloading UI](./ui-dev-mode.md) +- [Publishing Guide](./publishing.md) +- [How to develop an in-browser mocked UI](./ui-mock-mode.md) +- [How to live reload on local dependency changes](./developing-on-deps.md) +- [How to add new networks to the Provider Menu](./adding-new-networks.md) +- [How to manage notices that appear when the app starts up](./notices.md) +- [How to port MetaMask to a new platform](./porting_to_new_environment.md) +- [How to generate a visualization of this repository's development](./development-visualization.md)
\ No newline at end of file diff --git a/docs/translating-guide.md b/docs/translating-guide.md index 62d444b5a..ae2dfecd3 100644 --- a/docs/translating-guide.md +++ b/docs/translating-guide.md @@ -14,5 +14,13 @@ That's it! When MetaMask is loaded on a computer with that language set as the s ## Testing -To verify that your translation works, you will need to [build a local copy](https://github.com/MetaMask/metamask-extension#building-locally) of MetaMask. +To automatically see if you are missing any phrases to translate, we have a script you can run (if you know how to use the command line). The script is: + +``` +node development/verify-locale-strings.js $YOUR_LOCALE +``` + +Where `$YOUR_LOCALE` is your [locale string](https://r12a.github.io/app-subtags/), i.e. the name of your language folder. + +To verify that your translation works in the app, you will need to [build a local copy](https://github.com/MetaMask/metamask-extension#building-locally) of MetaMask. You will need to change your browser language, your operating system language, and restart your browser (sorry it's so much work!). diff --git a/gulpfile.js b/gulpfile.js index adfb148a9..f57ea6206 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -339,7 +339,7 @@ function generateBundler(opts, performBundle) { const browserifyOpts = assign({}, watchify.args, { entries: ['./app/scripts/'+opts.filename], plugin: 'browserify-derequire', - debug: debug, + debug: true, fullPaths: debug, }) @@ -405,13 +405,13 @@ function bundleTask(opts) { .pipe(buffer()) // sourcemaps // loads map from browserify file - .pipe(gulpif(debug, sourcemaps.init({ loadMaps: true }))) + .pipe(sourcemaps.init({ loadMaps: true })) // Minification .pipe(gulpif(opts.isBuild, uglify({ mangle: { reserved: [ 'MetamaskInpageProvider' ] }, }))) // writes .map file - .pipe(gulpif(debug, sourcemaps.write('./'))) + .pipe(sourcemaps.write(debug ? './' : '../../sourcemaps')) // write completed bundles .pipe(gulp.dest('./dist/firefox/scripts')) .pipe(gulp.dest('./dist/chrome/scripts')) diff --git a/mascara/src/app/first-time/backup-phrase-screen.js b/mascara/src/app/first-time/backup-phrase-screen.js index c8cc56c6c..6819abcf3 100644 --- a/mascara/src/app/first-time/backup-phrase-screen.js +++ b/mascara/src/app/first-time/backup-phrase-screen.js @@ -78,14 +78,16 @@ class BackupPhraseScreen extends Component { {this.props.seedWords} </div> {!isShowingSecret && ( - <div className="backup-phrase__secret-blocker"> + <div + className="backup-phrase__secret-blocker" + onClick={() => this.setState({ isShowingSecret: true })} + > <LockIcon width="28px" height="35px" fill="#FFFFFF" /> - <button + <div className="backup-phrase__reveal-button" - onClick={() => this.setState({ isShowingSecret: true })} > Click here to reveal secret words - </button> + </div> </div> )} </div> diff --git a/notices/README.md b/notices/README.md new file mode 100644 index 000000000..9362769c2 --- /dev/null +++ b/notices/README.md @@ -0,0 +1,5 @@ +# Notices + +Those notices are of legal nature. They are displayed to the users of MetaMask. + +Any changes or additions must be reviewed by the product management.
\ No newline at end of file diff --git a/old-ui/app/components/transaction-list-item.js b/old-ui/app/components/transaction-list-item.js index e7251df8d..7ab3414e5 100644 --- a/old-ui/app/components/transaction-list-item.js +++ b/old-ui/app/components/transaction-list-item.js @@ -29,9 +29,16 @@ function TransactionListItem () { } TransactionListItem.prototype.showRetryButton = function () { - const { transaction = {} } = this.props - const { status, time } = transaction - return status === 'submitted' && Date.now() - time > 30000 + const { transaction = {}, transactions } = this.props + const { status, submittedTime, txParams } = transaction + const currentNonce = txParams.nonce + const currentNonceTxs = transactions.filter(tx => tx.txParams.nonce === currentNonce) + const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted') + const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[0] + const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce + && lastSubmittedTxWithCurrentNonce.id === transaction.id + + return currentTxIsLatestWithNonce && Date.now() - submittedTime > 30000 } TransactionListItem.prototype.render = function () { @@ -201,6 +208,11 @@ function formatDate (date) { function renderErrorOrWarning (transaction) { const { status, err, warning } = transaction + // show dropped + if (status === 'dropped') { + return h('span.dropped', ' (Dropped)') + } + // show rejected if (status === 'rejected') { return h('span.error', ' (Rejected)') diff --git a/old-ui/app/components/transaction-list.js b/old-ui/app/components/transaction-list.js index 345e3ca16..c77852921 100644 --- a/old-ui/app/components/transaction-list.js +++ b/old-ui/app/components/transaction-list.js @@ -62,7 +62,7 @@ TransactionList.prototype.render = function () { } return h(TransactionListItem, { transaction, i, network, key, - conversionRate, + conversionRate, transactions, showTx: (txId) => { this.props.viewPendingTx(txId) }, diff --git a/old-ui/app/css/index.css b/old-ui/app/css/index.css index 67c327f62..7af713336 100644 --- a/old-ui/app/css/index.css +++ b/old-ui/app/css/index.css @@ -247,6 +247,10 @@ app sections color: #FFAE00; } +.dropped { + color: #6195ED; +} + .lock { width: 50px; height: 50px; diff --git a/package-lock.json b/package-lock.json index d1c488b09..5d1c46c26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -182,6 +182,48 @@ "through2": "2.0.3" } }, + "@sentry/cli": { + "version": "1.30.3", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.30.3.tgz", + "integrity": "sha1-AtD3eBwe5eG+WkMSoyX76LGzcjE=", + "dev": true, + "requires": { + "https-proxy-agent": "2.2.0", + "node-fetch": "1.7.3", + "progress": "2.0.0", + "proxy-from-env": "1.0.0" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz", + "integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==", + "dev": true, + "requires": { + "es6-promisify": "5.0.0" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "https-proxy-agent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.0.tgz", + "integrity": "sha512-uUWcfXHvy/dwfM9bqa6AozvAjS32dZSTUYd/4SEpYKRg6LEcPLshksnQYRudM9AyNvUARMfAg5TLjUDyX/K4vA==", + "dev": true, + "requires": { + "agent-base": "4.2.0", + "debug": "3.1.0" + } + } + } + }, "@types/node": { "version": "8.5.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-8.5.5.tgz", @@ -4932,6 +4974,21 @@ "event-emitter": "0.3.5" } }, + "es6-promise": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "4.2.4" + } + }, "es6-set": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", @@ -16914,6 +16971,12 @@ } } }, + "proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=", + "dev": true + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -17237,6 +17300,11 @@ "eve-raphael": "0.5.0" } }, + "raven-js": { + "version": "3.24.0", + "resolved": "https://registry.npmjs.org/raven-js/-/raven-js-3.24.0.tgz", + "integrity": "sha512-+/ygcWib8PXAE7Xq53j1tYxCgkzFyp9z05LYAKp2PA9KwO4Ek74q1tkGwZyPWI/FoXOgas6jNtQ7O3tdPif6uA==" + }, "raw-body": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", diff --git a/package.json b/package.json index 00587ece6..ac4758f57 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "mascara": "gulp build && cross-env METAMASK_DEBUG=true node ./mascara/example/server", "dist": "npm run dist:clear && npm install && gulp dist", "dist:clear": "rm -rf node_modules/eth-contract-metadata && rm -rf node_modules/eth-phishing-detect", - "test": "npm run lint && npm run test:coverage && npm run test:integration", + "test": "npm run test:unit && npm run test:integration && npm run lint", "test:unit": "cross-env METAMASK_ENV=test mocha --exit --require babel-core/register --require test/helper.js --recursive \"test/unit/**/*.js\"", "test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js", "test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara", @@ -25,10 +25,17 @@ "test:flat:build:states": "node development/genStates.js", "test:flat:build:ui": "npm run test:flat:build:states && browserify ./development/mock-dev.js -o ./development/bundle.js", "test:mascara": "npm run test:mascara:build && karma start test/mascara.conf.js", - "test:mascara:build": "mkdir -p dist/mascara && npm run test:mascara:build:ui && npm run test:mascara:build:background && npm run test:mascara:build:tests", + "test:mascara:build": "mkdirp dist/mascara && npm run test:mascara:build:ui && npm run test:mascara:build:background && npm run test:mascara:build:tests", "test:mascara:build:ui": "browserify mascara/test/test-ui.js -o dist/mascara/ui.js", "test:mascara:build:background": "browserify mascara/src/background.js -o dist/mascara/background.js", "test:mascara:build:tests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js", + "sentry": "export RELEASE=`cat app/manifest.json| jq -r .version` && npm run sentry:release && npm run sentry:upload", + "sentry:release": "npm run sentry:release:new && npm run sentry:release:clean", + "sentry:release:new": "sentry-cli releases --org 'metamask' --project 'metamask' new $RELEASE", + "sentry:release:clean": "sentry-cli releases --org 'metamask' --project 'metamask' files $RELEASE delete --all", + "sentry:upload": "npm run sentry:upload:source && npm run sentry:upload:maps", + "sentry:upload:source": "for FILEPATH in ./dist/chrome/scripts/*.js; do [ -e $FILEPATH ] || continue; export FILE=`basename $FILEPATH` && echo uploading $FILE && sentry-cli releases --org 'metamask' --project 'metamask' files $RELEASE upload $FILEPATH metamask/scripts/$FILE; done;", + "sentry:upload:maps": "sentry-cli releases --org 'metamask' --project 'metamask' files $RELEASE upload-sourcemaps ./dist/sourcemaps/ --url-prefix 'sourcemaps' --rewrite", "lint": "gulp lint", "lint:fix": "gulp lint:fix", "disc": "gulp disc --debug", @@ -144,6 +151,7 @@ "pumpify": "^1.3.4", "qrcode-npm": "0.0.3", "ramda": "^0.24.1", + "raven-js": "^3.24.0", "react": "^15.6.2", "react-addons-css-transition-group": "^15.6.0", "react-dom": "^15.6.2", @@ -179,6 +187,7 @@ "xtend": "^4.0.1" }, "devDependencies": { + "@sentry/cli": "^1.30.3", "babel-core": "^6.24.1", "babel-eslint": "^8.0.0", "babel-plugin-transform-async-to-generator": "^6.24.1", diff --git a/test/integration/lib/add-token.js b/test/integration/lib/add-token.js index 42ed28dca..228192e7b 100644 --- a/test/integration/lib/add-token.js +++ b/test/integration/lib/add-token.js @@ -26,7 +26,7 @@ async function runAddTokenFlowTest (assert, done) { assert.ok($('.token-list-item').length === 0, 'no tokens added') // Go to Add Token screen - let addTokenButton = await queryAsync($, 'button.btn-clear.wallet-view__add-token-button') + let addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button') assert.ok(addTokenButton[0], 'add token button present') addTokenButton[0].click() @@ -38,14 +38,14 @@ async function runAddTokenFlowTest (assert, done) { assert.equal(addTokenTitle[0].textContent, 'Add Token', 'add token title is correct') // Cancel Add Token - const cancelAddTokenButton = await queryAsync($, 'button.btn-cancel.add-token__button') + const cancelAddTokenButton = await queryAsync($, 'button.btn-secondary--lg.add-token__cancel-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-clear.wallet-view__add-token-button') + addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button') assert.ok(addTokenButton[0], 'add token button present') addTokenButton[0].click() @@ -68,7 +68,7 @@ async function runAddTokenFlowTest (assert, done) { tokenWrapper[0].click() // Click Next button - let nextButton = await queryAsync($, 'button.btn-clear.add-token__button') + let nextButton = await queryAsync($, 'button.btn-primary--lg') assert.equal(nextButton[0].textContent, 'Next', 'next button rendered') nextButton[0].click() @@ -78,8 +78,8 @@ async function runAddTokenFlowTest (assert, done) { 'Would you like to add these tokens?', 'confirm add token rendered' ) - assert.ok($('button.btn-clear.add-token__button')[0], 'confirm add token button found') - $('button.btn-clear.add-token__button')[0].click() + assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found') + $('button.btn-primary--lg')[0].click() // Verify added token image let heroBalance = await queryAsync($, '.hero-balance') @@ -87,7 +87,7 @@ async function runAddTokenFlowTest (assert, done) { assert.ok(tokenImageUrl.indexOf(heroBalance.find('img').attr('src')) > -1, 'token added') // Return to Add Token Screen - addTokenButton = await queryAsync($, 'button.btn-clear.wallet-view__add-token-button') + addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button') assert.ok(addTokenButton[0], 'add token button present') addTokenButton[0].click() @@ -101,14 +101,14 @@ async function runAddTokenFlowTest (assert, done) { reactTriggerChange(customInput[0]) // Click Next button - nextButton = await queryAsync($, 'button.btn-clear.add-token__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($, '.add-token__add-custom-error-message') assert.ok(errorMessage[0], 'error rendered') - $('button.btn-cancel.add-token__button')[0].click() + $('button.btn-secondary--lg')[0].click() // // Confirm Add token // assert.equal( @@ -116,8 +116,8 @@ async function runAddTokenFlowTest (assert, done) { // 'Would you like to add these tokens?', // 'confirm add token rendered' // ) - // assert.ok($('button.btn-clear.add-token__button')[0], 'confirm add token button found') - // $('button.btn-clear.add-token__button')[0].click() + // 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($, '.hero-balance') diff --git a/test/integration/lib/confirm-sig-requests.js b/test/integration/lib/confirm-sig-requests.js index 9737a2283..f1116d1a6 100644 --- a/test/integration/lib/confirm-sig-requests.js +++ b/test/integration/lib/confirm-sig-requests.js @@ -27,7 +27,7 @@ async function runConfirmSigRequestsTest(assert, done) { let confirmSigRowValue = await queryAsync($, '.request-signature__row-value') assert.ok(confirmSigRowValue[0].textContent.match(/^\#\sTerms\sof\sUse/)) - let confirmSigSignButton = await queryAsync($, '.request-signature__footer__sign-button') + let confirmSigSignButton = await queryAsync($, 'button.btn-primary--lg') confirmSigSignButton[0].click() confirmSigHeadline = await queryAsync($, '.request-signature__headline') @@ -39,7 +39,7 @@ async function runConfirmSigRequestsTest(assert, done) { confirmSigRowValue = await queryAsync($, '.request-signature__row-value') assert.equal(confirmSigRowValue[0].textContent, '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0') - confirmSigSignButton = await queryAsync($, '.request-signature__footer__sign-button') + confirmSigSignButton = await queryAsync($, 'button.btn-primary--lg') confirmSigSignButton[0].click() confirmSigHeadline = await queryAsync($, '.request-signature__headline') @@ -49,7 +49,7 @@ async function runConfirmSigRequestsTest(assert, done) { assert.equal(confirmSigRowValue[0].textContent, 'Hi, Alice!') assert.equal(confirmSigRowValue[1].textContent, '1337') - confirmSigSignButton = await queryAsync($, '.request-signature__footer__sign-button') + confirmSigSignButton = await queryAsync($, 'button.btn-primary--lg') confirmSigSignButton[0].click() const txView = await queryAsync($, '.tx-view') diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js index 5d21ba2a3..7bd9a7a65 100644 --- a/test/integration/lib/send-new-ui.js +++ b/test/integration/lib/send-new-ui.js @@ -1,4 +1,4 @@ -const reactTriggerChange = require('react-trigger-change') +const reactTriggerChange = require('../../lib/react-trigger-change') const { timeout, queryAsync, @@ -21,13 +21,15 @@ global.ethQuery = { sendTransaction: () => {}, } +global.ethereumProvider = {} + async function runSendFlowTest(assert, done) { console.log('*** start runSendFlowTest') const selectState = await queryAsync($, 'select') selectState.val('send new ui') reactTriggerChange(selectState[0]) - const sendScreenButton = await queryAsync($, 'button.btn-clear.hero-balance-button') + const sendScreenButton = await queryAsync($, 'button.btn-primary.hero-balance-button') assert.ok(sendScreenButton[1], 'send screen button present') sendScreenButton[1].click() @@ -93,7 +95,7 @@ async function runSendFlowTest(assert, done) { 'send gas field should show estimated gas total converted to USD' ) - const sendGasOpenCustomizeModalButton = await queryAsync($, '.send-v2__sliders-icon-container') + const sendGasOpenCustomizeModalButton = await queryAsync($, '.sliders-icon-container') sendGasOpenCustomizeModalButton[0].click() const customizeGasModal = await queryAsync($, '.send-v2__customize-gas') @@ -120,27 +122,28 @@ async function runSendFlowTest(assert, done) { 'send gas field should show customized gas total converted to USD' ) - const sendButton = await queryAsync($, 'button.btn-clear.page-container__footer-button') + const sendButton = await queryAsync($, 'button.btn-primary--lg.page-container__footer-button') assert.equal(sendButton[0].textContent, 'Next', 'next button rendered') sendButton[0].click() await timeout() selectState.val('send edit') reactTriggerChange(selectState[0]) + await timeout(10000) - const confirmFromName = (await queryAsync($, '.confirm-screen-account-name')).first() + const confirmFromName = (await queryAsync($, '.sender-to-recipient__sender-name')).first() assert.equal(confirmFromName[0].textContent, 'Send Account 2', 'confirm screen should show correct from name') - const confirmToName = (await queryAsync($, '.confirm-screen-account-name')).last() + const confirmToName = (await queryAsync($, '.sender-to-recipient__recipient-name')).last() assert.equal(confirmToName[0].textContent, 'Send Account 3', 'confirm screen should show correct to name') const confirmScreenRows = await queryAsync($, '.confirm-screen-rows') - const confirmScreenGas = confirmScreenRows.find('.confirm-screen-row-info')[2] - assert.equal(confirmScreenGas.textContent, '3.6 USD', 'confirm screen should show correct gas') - const confirmScreenTotal = confirmScreenRows.find('.confirm-screen-row-info')[3] + const confirmScreenGas = confirmScreenRows.find('.currency-display__converted-value')[0] + assert.equal(confirmScreenGas.textContent, '3.60 USD', 'confirm screen should show correct gas') + const confirmScreenTotal = confirmScreenRows.find('.confirm-screen-row-info')[2] assert.equal(confirmScreenTotal.textContent, '2405.36 USD', 'confirm screen should show correct total') - const confirmScreenBackButton = await queryAsync($, '.confirm-screen-back-button') + const confirmScreenBackButton = await queryAsync($, '.page-container__back-button') confirmScreenBackButton[0].click() const sendFromFieldItemInEdit = await queryAsync($, '.account-list-item') @@ -160,7 +163,7 @@ async function runSendFlowTest(assert, done) { sendAmountFieldInputInEdit.val('1.0') reactTriggerChange(sendAmountFieldInputInEdit[0]) - const sendButtonInEdit = await queryAsync($, '.btn-clear.page-container__footer-button') + const sendButtonInEdit = await queryAsync($, '.btn-primary--lg.page-container__footer-button') assert.equal(sendButtonInEdit[0].textContent, 'Next', 'next button in edit rendered') sendButtonInEdit[0].click() diff --git a/test/lib/react-trigger-change.js b/test/lib/react-trigger-change.js new file mode 100644 index 000000000..a25ddff00 --- /dev/null +++ b/test/lib/react-trigger-change.js @@ -0,0 +1,161 @@ +// Trigger React's synthetic change events on input, textarea and select elements +// https://github.com/vitalyq/react-trigger-change + +/******************IMPORTANT NOTE******************/ +/* This file is a modification of the */ +/* 'react-trigger-change' library linked above. */ +/* That library breaks when 'onFocus' events are */ +/* added to components under test because it */ +/* dispatches focus events to ensure changes are */ +/* triggered in some versions of IE. */ +/* This modification removes the accomodations */ +/* 'react-trigger-change' makes for IE to ensure */ +/* our tests can pass in chrome and firefox. */ +/**************************************************/ + +'use strict'; + +// Constants and functions are declared inside the closure. +// In this way, reactTriggerChange can be passed directly to executeScript in Selenium. +module.exports = function reactTriggerChange(node) { + var supportedInputTypes = { + color: true, + date: true, + datetime: true, + 'datetime-local': true, + email: true, + month: true, + number: true, + password: true, + range: true, + search: true, + tel: true, + text: true, + time: true, + url: true, + week: true + }; + var nodeName = node.nodeName.toLowerCase(); + var type = node.type; + var event; + var descriptor; + var initialValue; + var initialChecked; + var initialCheckedRadio; + + // Do not try to delete non-configurable properties. + // Value and checked properties on DOM elements are non-configurable in PhantomJS. + function deletePropertySafe(elem, prop) { + var desc = Object.getOwnPropertyDescriptor(elem, prop); + if (desc && desc.configurable) { + delete elem[prop]; + } + } + + function getCheckedRadio(radio) { + var name = radio.name; + var radios; + var i; + if (name) { + radios = document.querySelectorAll('input[type="radio"][name="' + name + '"]'); + for (i = 0; i < radios.length; i += 1) { + if (radios[i].checked) { + return radios[i] !== radio ? radios[i] : null; + } + } + } + return null; + } + + function preventChecking(e) { + e.preventDefault(); + if (!initialChecked) { + e.target.checked = false; + } + if (initialCheckedRadio) { + initialCheckedRadio.checked = true; + } + } + + if (nodeName === 'select' || + (nodeName === 'input' && type === 'file')) { + // IE9-IE11, non-IE + // Dispatch change. + event = document.createEvent('HTMLEvents'); + event.initEvent('change', true, false); + node.dispatchEvent(event); + } else if ((nodeName === 'input' && supportedInputTypes[type]) || + nodeName === 'textarea') { + // React 16 + // Cache artificial value property descriptor. + // Property doesn't exist in React <16, descriptor is undefined. + descriptor = Object.getOwnPropertyDescriptor(node, 'value'); + + // Update inputValueTracking cached value. + // Remove artificial value property. + // Restore initial value to trigger event with it. + initialValue = node.value; + node.value = initialValue + '#'; + deletePropertySafe(node, 'value'); + node.value = initialValue; + + // React 0.14: IE10-IE11, non-IE + // React 15: non-IE + // React 16: IE10-IE11, non-IE + event = document.createEvent('HTMLEvents'); + event.initEvent('input', true, false); + node.dispatchEvent(event); + + // React 16 + // Restore artificial value property descriptor. + if (descriptor) { + Object.defineProperty(node, 'value', descriptor); + } + } else if (nodeName === 'input' && type === 'checkbox') { + // Invert inputValueTracking cached value. + node.checked = !node.checked; + + // Dispatch click. + // Click event inverts checked value. + event = document.createEvent('MouseEvents'); + event.initEvent('click', true, true); + node.dispatchEvent(event); + } else if (nodeName === 'input' && type === 'radio') { + // Cache initial checked value. + initialChecked = node.checked; + + // Find and cache initially checked radio in the group. + initialCheckedRadio = getCheckedRadio(node); + + // React 16 + // Cache property descriptor. + // Invert inputValueTracking cached value. + // Remove artificial checked property. + // Restore initial value, otherwise preventDefault will eventually revert the value. + descriptor = Object.getOwnPropertyDescriptor(node, 'checked'); + node.checked = !initialChecked; + deletePropertySafe(node, 'checked'); + node.checked = initialChecked; + + // Prevent toggling during event capturing phase. + // Set checked value to false if initialChecked is false, + // otherwise next listeners will see true. + // Restore initially checked radio in the group. + node.addEventListener('click', preventChecking, true); + + // Dispatch click. + // Click event inverts checked value. + event = document.createEvent('MouseEvents'); + event.initEvent('click', true, true); + node.dispatchEvent(event); + + // Remove listener to stop further change prevention. + node.removeEventListener('click', preventChecking, true); + + // React 16 + // Restore artificial checked property descriptor. + if (descriptor) { + Object.defineProperty(node, 'checked', descriptor); + } + } +}; diff --git a/test/unit/components/binary-renderer-test.js b/test/unit/components/binary-renderer-test.js index ee2fa8b60..7bf9250cc 100644 --- a/test/unit/components/binary-renderer-test.js +++ b/test/unit/components/binary-renderer-test.js @@ -1,5 +1,5 @@ var assert = require('assert') -var BinaryRenderer = require('../../../ui/app/components/binary-renderer') +var BinaryRenderer = require('../../../old-ui/app/components/binary-renderer') describe('BinaryRenderer', function () { let binaryRenderer diff --git a/test/unit/components/bn-as-decimal-input-test.js b/test/unit/components/bn-as-decimal-input-test.js index 58ecc9c89..7b9d9814f 100644 --- a/test/unit/components/bn-as-decimal-input-test.js +++ b/test/unit/components/bn-as-decimal-input-test.js @@ -6,7 +6,7 @@ const ReactTestUtils = require('react-addons-test-utils') const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN -var BnInput = require('../../../ui/app/components/bn-as-decimal-input') +var BnInput = require('../../../old-ui/app/components/bn-as-decimal-input') describe('BnInput', function () { it('can tolerate a gas decimal number at a high precision', function (done) { diff --git a/test/unit/nameForAccount_test.js b/test/unit/nameForAccount_test.js index e7c0b18b4..32af49e9d 100644 --- a/test/unit/nameForAccount_test.js +++ b/test/unit/nameForAccount_test.js @@ -2,7 +2,7 @@ var assert = require('assert') var sinon = require('sinon') var path = require('path') -var contractNamer = require(path.join(__dirname, '..', '..', 'ui', 'lib', 'contract-namer.js')) +var contractNamer = require(path.join(__dirname, '..', '..', 'old-ui', 'lib', 'contract-namer.js')) describe('contractNamer', function () { beforeEach(function () { diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index cc99afee4..712097fce 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -392,6 +392,49 @@ describe('Transaction Controller', function () { }) }) + describe('#retryTransaction', function () { + it('should create a new txMeta with the same txParams as the original one', function (done) { + let txParams = { + nonce: '0x00', + from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4', + to: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4', + data: '0x0', + } + txController.txStateManager._saveTxList([ + { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams }, + ]) + txController.retryTransaction(1) + .then((txMeta) => { + assert.equal(txMeta.txParams.nonce, txParams.nonce, 'nonce should be the same') + assert.equal(txMeta.txParams.from, txParams.from, 'from should be the same') + assert.equal(txMeta.txParams.to, txParams.to, 'to should be the same') + assert.equal(txMeta.txParams.data, txParams.data, 'data should be the same') + assert.ok(('lastGasPrice' in txMeta), 'should have the key `lastGasPrice`') + assert.equal(txController.txStateManager.getTxList().length, 2) + done() + }).catch(done) + }) + }) + + describe('#_markNonceDuplicatesDropped', function () { + it('should mark all nonce duplicates as dropped without marking the confirmed transaction as dropped', function () { + txController.txStateManager._saveTxList([ + { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 4, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 5, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 6, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + ]) + txController._markNonceDuplicatesDropped(1) + const confirmedTx = txController.txStateManager.getTx(1) + const droppedTxs = txController.txStateManager.getFilteredTxList({ nonce: '0x01', status: 'dropped' }) + assert.equal(confirmedTx.status, 'confirmed', 'the confirmedTx should remain confirmed') + assert.equal(droppedTxs.length, 6, 'their should be 6 dropped txs') + + }) + }) describe('#getPendingTransactions', function () { beforeEach(function () { @@ -401,7 +444,7 @@ describe('Transaction Controller', function () { { id: 3, status: 'approved', metamaskNetworkId: currentNetworkId, txParams: {} }, { id: 4, status: 'signed', metamaskNetworkId: currentNetworkId, txParams: {} }, { id: 5, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 6, status: 'confimed', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 6, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, { id: 7, status: 'failed', metamaskNetworkId: currentNetworkId, txParams: {} }, ]) }) diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js deleted file mode 100644 index 0da435298..000000000 --- a/ui/app/account-detail.js +++ /dev/null @@ -1,117 +0,0 @@ -const inherits = require('util').inherits -const extend = require('xtend') -const Component = require('react').Component -const h = require('react-hyperscript') -const connect = require('react-redux').connect -const actions = require('./actions') -const valuesFor = require('./util').valuesFor -const TransactionList = require('./components/transaction-list') -const ExportAccountView = require('./components/account-export') -const TabBar = require('./components/tab-bar') -const TokenList = require('./components/token-list') - -module.exports = connect(mapStateToProps)(AccountDetailScreen) - -function mapStateToProps (state) { - return { - metamask: state.metamask, - identities: state.metamask.identities, - accounts: state.metamask.accounts, - address: state.metamask.selectedAddress, - accountDetail: state.appState.accountDetail, - network: state.metamask.network, - unapprovedMsgs: valuesFor(state.metamask.unapprovedMsgs), - shapeShiftTxList: state.metamask.shapeShiftTxList, - transactions: state.metamask.selectedAddressTxList || [], - conversionRate: state.metamask.conversionRate, - currentCurrency: state.metamask.currentCurrency, - currentAccountTab: state.metamask.currentAccountTab, - tokens: state.metamask.tokens, - computedBalances: state.metamask.computedBalances, - } -} - -inherits(AccountDetailScreen, Component) -function AccountDetailScreen () { - Component.call(this) -} - -// Note: This component is no longer used. Leaving the file for reference: -// - structuring routing for add token -// - state required for TxList -// Delete file when those features are complete -AccountDetailScreen.prototype.render = function () {} - -AccountDetailScreen.prototype.subview = function () { - var subview - try { - subview = this.props.accountDetail.subview - } catch (e) { - subview = null - } - - switch (subview) { - case 'transactions': - return this.tabSections() - case 'export': - var state = extend({key: 'export'}, this.props) - return h(ExportAccountView, state) - default: - return this.tabSections() - } -} - -AccountDetailScreen.prototype.tabSections = function () { - const { currentAccountTab } = this.props - - return h('section.tabSection.full-flex-height.grow-tenx', [ - - h(TabBar, { - tabs: [ - { content: 'Sent', key: 'history' }, - { content: 'Tokens', key: 'tokens' }, - ], - defaultTab: currentAccountTab || 'history', - tabSelected: (key) => { - this.props.dispatch(actions.setCurrentAccountTab(key)) - }, - }), - - this.tabSwitchView(), - ]) -} - -AccountDetailScreen.prototype.tabSwitchView = function () { - const props = this.props - const { address, network } = props - const { currentAccountTab, tokens } = this.props - - switch (currentAccountTab) { - case 'tokens': - return h(TokenList, { - userAddress: address, - network, - tokens, - addToken: () => this.props.dispatch(actions.showAddTokenPage()), - }) - default: - return this.transactionList() - } -} - -AccountDetailScreen.prototype.transactionList = function () { - const {transactions, unapprovedMsgs, address, - network, shapeShiftTxList, conversionRate } = this.props - - return h(TransactionList, { - transactions: transactions.sort((a, b) => b.time - a.time), - network, - unapprovedMsgs, - conversionRate, - address, - shapeShiftTxList, - viewPendingTx: (txId) => { - this.props.dispatch(actions.viewPendingTx(txId)) - }, - }) -} diff --git a/ui/app/accounts/import/index.js b/ui/app/accounts/import/index.js index c1b190e3d..fc9031a65 100644 --- a/ui/app/accounts/import/index.js +++ b/ui/app/accounts/import/index.js @@ -37,7 +37,7 @@ AccountImportSubview.prototype.render = function () { h('div.new-account-import-form', [ h('.new-account-import-disclaimer', [ - h('span', 'Imported accounts will not be associated with your originally created MetaMask account seedphrase. Learn more about imported accounts '), + h('span', t('importAccountMsg')), h('span', { style: { cursor: 'pointer', @@ -48,12 +48,12 @@ AccountImportSubview.prototype.render = function () { url: 'https://metamask.helpscoutdocs.com/article/17-what-are-loose-accounts', }) }, - }, 'here'), + }, t('here')), ]), h('div.new-account-import-form__select-section', [ - h('div.new-account-import-form__select-label', 'Select Type'), + h('div.new-account-import-form__select-label', t('selectType')), h(Select, { className: 'new-account-import-form__select', diff --git a/ui/app/accounts/import/json.js b/ui/app/accounts/import/json.js index 1b5e485d7..eeba73e77 100644 --- a/ui/app/accounts/import/json.js +++ b/ui/app/accounts/import/json.js @@ -50,13 +50,13 @@ class JsonImportSubview extends Component { h('div.new-account-create-form__buttons', {}, [ - h('button.new-account-create-form__button-cancel', { + h('button.btn-secondary.new-account-create-form__button', { onClick: () => this.props.goHome(), }, [ t('cancel'), ]), - h('button.new-account-create-form__button-create', { + h('button.btn-primary.new-account-create-form__button', { onClick: () => this.createNewKeychain(), }, [ t('import'), @@ -84,7 +84,7 @@ class JsonImportSubview extends Component { const state = this.state if (!state) { - const message = 'You must select a valid file to import.' + const message = t('validFileImport') return this.props.displayWarning(message) } @@ -102,7 +102,7 @@ class JsonImportSubview extends Component { const message = t('needImportPassword') return this.props.displayWarning(message) } - + this.props.importNewJsonAccount([ fileContents, password ]) } } diff --git a/ui/app/accounts/import/private-key.js b/ui/app/accounts/import/private-key.js index bc9e9384e..13c8da722 100644 --- a/ui/app/accounts/import/private-key.js +++ b/ui/app/accounts/import/private-key.js @@ -48,13 +48,13 @@ PrivateKeyImportView.prototype.render = function () { h('div.new-account-import-form__buttons', {}, [ - h('button.new-account-create-form__button-cancel.allcaps', { + h('button.btn-secondary--lg.new-account-create-form__button', { onClick: () => goHome(), }, [ t('cancel'), ]), - h('button.new-account-create-form__button-create.allcaps', { + h('button.btn-primary--lg.new-account-create-form__button', { onClick: () => this.createNewKeychain(), }, [ t('import'), diff --git a/ui/app/accounts/new-account/create-form.js b/ui/app/accounts/new-account/create-form.js index 8ef842a2a..c820cdf6d 100644 --- a/ui/app/accounts/new-account/create-form.js +++ b/ui/app/accounts/new-account/create-form.js @@ -20,7 +20,7 @@ class NewAccountCreateForm extends Component { render () { const { newAccountName, defaultAccountName } = this.state - + return h('div.new-account-create-form', [ @@ -38,13 +38,13 @@ class NewAccountCreateForm extends Component { h('div.new-account-create-form__buttons', {}, [ - h('button.new-account-create-form__button-cancel.allcaps', { + h('button.btn-secondary--lg.new-account-create-form__button', { onClick: () => this.props.goHome(), }, [ t('cancel'), ]), - h('button.new-account-create-form__button-create.allcaps', { + h('button.btn-primary--lg.new-account-create-form__button', { onClick: () => this.props.createAccount(newAccountName || defaultAccountName), }, [ t('create'), diff --git a/ui/app/actions.js b/ui/app/actions.js index 092af080b..4a5962610 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -694,10 +694,10 @@ function updateSendFrom (from) { } } -function updateSendTo (to) { +function updateSendTo (to, nickname = '') { return { type: actions.UPDATE_SEND_TO, - value: to, + value: { to, nickname }, } } @@ -1278,8 +1278,10 @@ function retryTransaction (txId) { if (err) { return dispatch(actions.displayWarning(err.message)) } + const { selectedAddressTxList } = newState + const { id: newTxId } = selectedAddressTxList[selectedAddressTxList.length - 1] dispatch(actions.updateMetamaskState(newState)) - dispatch(actions.viewPendingTx(txId)) + dispatch(actions.viewPendingTx(newTxId)) }) } } diff --git a/ui/app/add-token.js b/ui/app/add-token.js index b8878b772..edeea11d8 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -26,6 +26,7 @@ const fuse = new Fuse(contractList, { const actions = require('./actions') const ethUtil = require('ethereumjs-util') const { tokenInfoGetter } = require('./token-util') +const t = require('../i18n') const emptyAddr = '0x0000000000000000000000000000000000000000' @@ -139,28 +140,28 @@ AddTokenScreen.prototype.validate = function () { if (customAddress) { const validAddress = ethUtil.isValidAddress(customAddress) if (!validAddress) { - errors.customAddress = 'Address is invalid. ' + errors.customAddress = t('invalidAddress') } const validDecimals = customDecimals !== null && customDecimals >= 0 && customDecimals < 36 if (!validDecimals) { - errors.customDecimals = 'Decimals must be at least 0, and not over 36.' + errors.customDecimals = t('decimalsMustZerotoTen') } const symbolLen = customSymbol.trim().length const validSymbol = symbolLen > 0 && symbolLen < 10 if (!validSymbol) { - errors.customSymbol = 'Symbol must be between 0 and 10 characters.' + errors.customSymbol = t('symbolBetweenZeroTen') } const ownAddress = identitiesList.includes(standardAddress) if (ownAddress) { - errors.customAddress = 'Personal address detected. Input the token contract address.' + errors.customAddress = t('personalAddressDetected') } const tokenAlreadyAdded = this.checkExistingAddresses(customAddress) if (tokenAlreadyAdded) { - errors.customAddress = 'Token has already been added.' + errors.customAddress = t('tokenAlreadyAdded') } } else if ( Object.entries(selectedTokens) @@ -168,7 +169,7 @@ AddTokenScreen.prototype.validate = function () { isEmpty && !isSelected ), true) ) { - errors.tokenSelector = 'Must select at least 1 token.' + errors.tokenSelector = t('mustSelectOne') } return { @@ -198,7 +199,7 @@ AddTokenScreen.prototype.renderCustomForm = function () { 'add-token__add-custom-field--error': errors.customAddress, }), }, [ - h('div.add-token__add-custom-label', 'Token Address'), + h('div.add-token__add-custom-label', t('tokenAddress')), h('input.add-token__add-custom-input', { type: 'text', onChange: this.tokenAddressDidChange, @@ -211,7 +212,7 @@ AddTokenScreen.prototype.renderCustomForm = function () { 'add-token__add-custom-field--error': errors.customSymbol, }), }, [ - h('div.add-token__add-custom-label', 'Token Symbol'), + h('div.add-token__add-custom-label', t('tokenSymbol')), h('input.add-token__add-custom-input', { type: 'text', onChange: this.tokenSymbolDidChange, @@ -225,7 +226,7 @@ AddTokenScreen.prototype.renderCustomForm = function () { 'add-token__add-custom-field--error': errors.customDecimals, }), }, [ - h('div.add-token__add-custom-label', 'Decimals of Precision'), + h('div.add-token__add-custom-label', t('decimal')), h('input.add-token__add-custom-input', { type: 'number', onChange: this.tokenDecimalsDidChange, @@ -299,11 +300,11 @@ AddTokenScreen.prototype.renderConfirmation = function () { h('div.add-token', [ h('div.add-token__wrapper', [ h('div.add-token__title-container.add-token__confirmation-title', [ - h('div.add-token__title', 'Add Token'), - h('div.add-token__description', 'Would you like to add these tokens?'), + h('div.add-token__title', t('addToken')), + h('div.add-token__description', t('likeToAddTokens')), ]), h('div.add-token__content-container.add-token__confirmation-content', [ - h('div.add-token__description.add-token__confirmation-description', 'Your balances'), + h('div.add-token__description.add-token__confirmation-description', t('balances')), h('div.add-token__confirmation-token-list', Object.entries(tokens) .map(([ address, token ]) => ( @@ -320,12 +321,12 @@ AddTokenScreen.prototype.renderConfirmation = function () { ]), ]), h('div.add-token__buttons', [ - h('button.btn-cancel.add-token__button', { + h('button.btn-secondary--lg.add-token__cancel-button', { onClick: () => this.setState({ isShowingConfirmation: false }), - }, 'Back'), - h('button.btn-clear.add-token__button', { + }, t('back')), + h('button.btn-primary--lg', { onClick: () => addTokens(tokens).then(goHome), - }, 'Add Tokens'), + }, t('addTokens')), ]), ]) ) @@ -341,15 +342,15 @@ AddTokenScreen.prototype.render = function () { h('div.add-token', [ h('div.add-token__wrapper', [ h('div.add-token__title-container', [ - h('div.add-token__title', 'Add Token'), - h('div.add-token__description', 'Keep track of the tokens you’ve bought with your MetaMask account. If you bought tokens using a different account, those tokens will not appear here.'), - h('div.add-token__description', 'Search for tokens or select from our list of popular tokens.'), + h('div.add-token__title', t('addToken')), + h('div.add-token__description', t('tokenWarning1')), + h('div.add-token__description', t('tokenSelection')), ]), h('div.add-token__content-container', [ h('div.add-token__input-container', [ h('input.add-token__input', { type: 'text', - placeholder: 'Search', + placeholder: t('search'), onChange: e => this.setState({ searchQuery: e.target.value }), }), h('div.add-token__search-input-error-message', errors.tokenSelector), @@ -363,19 +364,19 @@ AddTokenScreen.prototype.render = function () { h('div.add-token__add-custom', { onClick: () => this.setState({ isCollapsed: !isCollapsed }), }, [ - 'Add custom token', + t('addCustomToken'), h(`i.fa.fa-angle-${isCollapsed ? 'down' : 'up'}`), ]), this.renderCustomForm(), ]), ]), h('div.add-token__buttons', [ - h('button.btn-cancel.add-token__button', { + h('button.btn-secondary--lg.add-token__cancel-button', { onClick: goHome, - }, 'Cancel'), - h('button.btn-clear.add-token__button', { + }, t('cancel')), + h('button.btn-primary--lg', { onClick: this.onNext, - }, 'Next'), + }, t('next')), ]), ]) ) diff --git a/ui/app/app.js b/ui/app/app.js index 954299a6a..6d9296131 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -132,7 +132,7 @@ App.prototype.render = function () { } = props const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config' const loadMessage = loadingMessage || isLoadingNetwork ? - `Connecting to ${this.getNetworkName()}` : null + this.getConnectingLabel() : null log.debug('Main ui render function') return ( @@ -550,6 +550,27 @@ App.prototype.toggleMetamaskActive = function () { } } +App.prototype.getConnectingLabel = function () { + const { provider } = this.props + const providerName = provider.type + + let name + + if (providerName === 'mainnet') { + name = t('connectingToMainnet') + } else if (providerName === 'ropsten') { + name = t('connectingToRopsten') + } else if (providerName === 'kovan') { + name = t('connectingToRopsten') + } else if (providerName === 'rinkeby') { + name = t('connectingToRinkeby') + } else { + name = t('connectingToUnknown') + } + + return name +} + App.prototype.getNetworkName = function () { const { provider } = this.props const providerName = provider.type @@ -557,15 +578,15 @@ App.prototype.getNetworkName = function () { let name if (providerName === 'mainnet') { - name = 'Main Ethereum Network' + name = t('mainnet') } else if (providerName === 'ropsten') { - name = 'Ropsten Test Network' + name = t('ropsten') } else if (providerName === 'kovan') { - name = 'Kovan Test Network' + name = t('kovan') } else if (providerName === 'rinkeby') { - name = 'Rinkeby Test Network' + name = t('rinkeby') } else { - name = 'Unknown Private Network' + name = t('unknownNetwork') } return name diff --git a/ui/app/components/balance.js b/ui/app/components/balance.js deleted file mode 100644 index 57ca84564..000000000 --- a/ui/app/components/balance.js +++ /dev/null @@ -1,89 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const formatBalance = require('../util').formatBalance -const generateBalanceObject = require('../util').generateBalanceObject -const Tooltip = require('./tooltip.js') -const FiatValue = require('./fiat-value.js') - -module.exports = EthBalanceComponent - -inherits(EthBalanceComponent, Component) -function EthBalanceComponent () { - Component.call(this) -} - -EthBalanceComponent.prototype.render = function () { - var props = this.props - let { value } = props - var style = props.style - var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true - value = value ? formatBalance(value, 6, needsParse) : '...' - var width = props.width - - return ( - - h('.ether-balance.ether-balance-amount', { - style: style, - }, [ - h('div', { - style: { - display: 'inline', - width: width, - }, - }, this.renderBalance(value)), - ]) - - ) -} -EthBalanceComponent.prototype.renderBalance = function (value) { - var props = this.props - if (value === 'None') return value - if (value === '...') return value - var balanceObj = generateBalanceObject(value, props.shorten ? 1 : 3) - var balance - var splitBalance = value.split(' ') - var ethNumber = splitBalance[0] - var ethSuffix = splitBalance[1] - const showFiat = 'showFiat' in props ? props.showFiat : true - - if (props.shorten) { - balance = balanceObj.shortBalance - } else { - balance = balanceObj.balance - } - - var label = balanceObj.label - - return ( - h(Tooltip, { - position: 'bottom', - title: `${ethNumber} ${ethSuffix}`, - }, h('div.flex-column', [ - h('.flex-row', { - style: { - alignItems: 'flex-end', - lineHeight: '13px', - fontFamily: 'Montserrat Light', - textRendering: 'geometricPrecision', - }, - }, [ - h('div', { - style: { - width: '100%', - textAlign: 'right', - }, - }, this.props.incoming ? `+${balance}` : balance), - h('div', { - style: { - color: ' #AEAEAE', - fontSize: '12px', - marginLeft: '5px', - }, - }, label), - ]), - - showFiat ? h(FiatValue, { value: props.value }) : null, - ])) - ) -} diff --git a/ui/app/components/binary-renderer.js b/ui/app/components/binary-renderer.js deleted file mode 100644 index 0b6a1f5c2..000000000 --- a/ui/app/components/binary-renderer.js +++ /dev/null @@ -1,46 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const ethUtil = require('ethereumjs-util') -const extend = require('xtend') - -module.exports = BinaryRenderer - -inherits(BinaryRenderer, Component) -function BinaryRenderer () { - Component.call(this) -} - -BinaryRenderer.prototype.render = function () { - const props = this.props - const { value, style } = props - const text = this.hexToText(value) - - const defaultStyle = extend({ - width: '315px', - maxHeight: '210px', - resize: 'none', - border: 'none', - background: 'white', - padding: '3px', - }, style) - - return ( - h('textarea.font-small', { - readOnly: true, - style: defaultStyle, - defaultValue: text, - }) - ) -} - -BinaryRenderer.prototype.hexToText = function (hex) { - try { - const stripped = ethUtil.stripHexPrefix(hex) - const buff = Buffer.from(stripped, 'hex') - return buff.toString('utf8') - } catch (e) { - return hex - } -} - diff --git a/ui/app/components/currency-input.js b/ui/app/components/currency-input.js index 6f7862e51..ece3eb43d 100644 --- a/ui/app/components/currency-input.js +++ b/ui/app/components/currency-input.js @@ -8,8 +8,12 @@ inherits(CurrencyInput, Component) function CurrencyInput (props) { Component.call(this) + const sanitizedValue = sanitizeValue(props.value) + this.state = { - value: sanitizeValue(props.value), + value: sanitizedValue, + emptyState: false, + focused: false, } } @@ -58,9 +62,11 @@ CurrencyInput.prototype.handleChange = function (newValue) { if (value === '0' && newValue[newValueLastIndex] === '0') { parsedValue = parsedValue.slice(0, newValueLastIndex) } - const sanitizedValue = sanitizeValue(parsedValue) - this.setState({ value: sanitizedValue }) + this.setState({ + value: sanitizedValue, + emptyState: newValue === '' && sanitizedValue === '0', + }) onInputChange(sanitizedValue) } @@ -85,18 +91,22 @@ CurrencyInput.prototype.render = function () { placeholder, readOnly, inputRef, + type, } = this.props + const { emptyState, focused } = this.state const inputSizeMultiplier = readOnly ? 1 : 1.2 const valueToRender = this.getValueToRender() - return h('input', { className, - value: valueToRender, - placeholder, + type, + value: emptyState ? '' : valueToRender, + placeholder: focused ? '' : placeholder, size: valueToRender.length * inputSizeMultiplier, readOnly, + onFocus: () => this.setState({ focused: true, emptyState: valueToRender === '0' }), + onBlur: () => this.setState({ focused: false, emptyState: false }), onChange: e => this.handleChange(e.target.value), ref: inputRef, }) diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index 920dfeab6..22ad98ce4 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -22,12 +22,14 @@ const { conversionUtil, multiplyCurrencies, conversionGreaterThan, + conversionMax, subtractCurrencies, } = require('../../conversion-util') const { getGasPrice, getGasLimit, + getForceGasMin, conversionRateSelector, getSendAmount, getSelectedToken, @@ -45,6 +47,7 @@ function mapStateToProps (state) { return { gasPrice: getGasPrice(state), gasLimit: getGasLimit(state), + forceGasMin: getForceGasMin(state), conversionRate, amount: getSendAmount(state), maxModeOn: getSendMaxModeState(state), @@ -115,9 +118,9 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) { updateSendAmount(maxAmount) } - updateGasPrice(gasPrice) - updateGasLimit(gasLimit) - updateGasTotal(gasTotal) + updateGasPrice(ethUtil.addHexPrefix(gasPrice)) + updateGasLimit(ethUtil.addHexPrefix(gasLimit)) + updateGasTotal(ethUtil.addHexPrefix(gasTotal)) hideModal() } @@ -218,7 +221,7 @@ CustomizeGasModal.prototype.convertAndSetGasPrice = function (newGasPrice) { } CustomizeGasModal.prototype.render = function () { - const { hideModal } = this.props + const { hideModal, forceGasMin } = this.props const { gasPrice, gasLimit, gasTotal, error, priceSigZeros, priceSigDec } = this.state let convertedGasPrice = conversionUtil(gasPrice, { @@ -230,6 +233,22 @@ CustomizeGasModal.prototype.render = function () { convertedGasPrice += convertedGasPrice.match(/[.]/) ? priceSigZeros : `${priceSigDec}${priceSigZeros}` + let newGasPrice = gasPrice + if (forceGasMin) { + const convertedMinPrice = conversionUtil(forceGasMin, { + fromNumericBase: 'hex', + toNumericBase: 'dec', + }) + convertedGasPrice = conversionMax( + { value: convertedMinPrice, fromNumericBase: 'dec' }, + { value: convertedGasPrice, fromNumericBase: 'dec' } + ) + newGasPrice = conversionMax( + { value: gasPrice, fromNumericBase: 'hex' }, + { value: forceGasMin, fromNumericBase: 'hex' } + ) + } + const convertedGasLimit = conversionUtil(gasLimit, { fromNumericBase: 'hex', toNumericBase: 'dec', @@ -252,7 +271,7 @@ CustomizeGasModal.prototype.render = function () { h(GasModalCard, { value: convertedGasPrice, - min: MIN_GAS_PRICE_GWEI, + min: forceGasMin || MIN_GAS_PRICE_GWEI, // max: 1000, step: multiplyCurrencies(MIN_GAS_PRICE_GWEI, 10), onChange: value => this.convertAndSetGasPrice(value), @@ -283,12 +302,16 @@ CustomizeGasModal.prototype.render = function () { }, [t('revert')]), h('div.send-v2__customize-gas__buttons', [ - h('div.send-v2__customize-gas__cancel.allcaps', { + h('button.btn-secondary.send-v2__customize-gas__cancel', { onClick: this.props.hideModal, + style: { + marginRight: '10px', + }, }, [t('cancel')]), - h(`div.send-v2__customize-gas__save${error ? '__error' : ''}.allcaps`, { - onClick: () => !error && this.save(gasPrice, gasLimit, gasTotal), + h('button.btn-primary.send-v2__customize-gas__save', { + onClick: () => !error && this.save(newGasPrice, gasLimit, gasTotal), + className: error && 'btn-primary--disabled', }, [t('save')]), ]), diff --git a/ui/app/components/dropdowns/account-options-dropdown.js b/ui/app/components/dropdowns/account-options-dropdown.js deleted file mode 100644 index f74c0a2d4..000000000 --- a/ui/app/components/dropdowns/account-options-dropdown.js +++ /dev/null @@ -1,29 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const AccountDropdowns = require('./components/account-dropdowns') - -inherits(AccountOptionsDropdown, Component) -function AccountOptionsDropdown () { - Component.call(this) -} - -module.exports = AccountOptionsDropdown - -// TODO: specify default props and proptypes -// TODO: hook up to state, connect to redux to clean up API -// TODO: selectedAddress is not defined... should we use selected? -AccountOptionsDropdown.prototype.render = function () { - const { selected, network, identities, style, dropdownWrapperStyle, menuItemStyles } = this.props - - return h(AccountDropdowns, { - enableAccountOptions: true, - enableAccountsSelector: false, - selected, - network, - identities, - style: style || {}, - dropdownWrapperStyle: dropdownWrapperStyle || {}, - menuItemStyles: menuItemStyles || {}, - }, []) -} diff --git a/ui/app/components/dropdowns/account-selection-dropdown.js b/ui/app/components/dropdowns/account-selection-dropdown.js deleted file mode 100644 index 2f6452b15..000000000 --- a/ui/app/components/dropdowns/account-selection-dropdown.js +++ /dev/null @@ -1,29 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const AccountDropdowns = require('./components/account-dropdowns') - -inherits(AccountSelectionDropdown, Component) -function AccountSelectionDropdown () { - Component.call(this) -} - -module.exports = AccountSelectionDropdown - -// TODO: specify default props and proptypes -// TODO: hook up to state, connect to redux to clean up API -// TODO: selectedAddress is not defined... should we use selected? -AccountSelectionDropdown.prototype.render = function () { - const { selected, network, identities, style, dropdownWrapperStyle, menuItemStyles } = this.props - - return h(AccountDropdowns, { - enableAccountOptions: false, - enableAccountsSelector: true, - selected, - network, - identities, - style: style || {}, - dropdownWrapperStyle: dropdownWrapperStyle || {}, - menuItemStyles: menuItemStyles || {}, - }, []) -} diff --git a/ui/app/components/dropdowns/index.js b/ui/app/components/dropdowns/index.js index fa66f5000..605507058 100644 --- a/ui/app/components/dropdowns/index.js +++ b/ui/app/components/dropdowns/index.js @@ -1,17 +1,11 @@ // Reusable Dropdown Components // TODO: Refactor into separate components const Dropdown = require('./components/dropdown').Dropdown -const AccountDropdowns = require('./components/account-dropdowns') // App-Specific Instances -const AccountSelectionDropdown = require('./account-selection-dropdown') -const AccountOptionsDropdown = require('./account-options-dropdown') const NetworkDropdown = require('./network-dropdown').default module.exports = { - AccountSelectionDropdown, - AccountOptionsDropdown, NetworkDropdown, Dropdown, - AccountDropdowns, } diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index add67ea35..1b2d4009d 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -9,6 +9,7 @@ const networkMap = require('ethjs-ens/lib/network-map.json') const ensRE = /.+\..+$/ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' const t = require('../../i18n') +const ToAutoComplete = require('./send/to-autocomplete') module.exports = EnsInput @@ -22,12 +23,14 @@ EnsInput.prototype.render = function () { const props = this.props const opts = extend(props, { list: 'addresses', - onChange: () => { + onChange: (recipient) => { const network = this.props.network const networkHasEnsSupport = getNetworkEnsSupport(network) + if (!networkHasEnsSupport) return - const recipient = document.querySelector('input[name="address"]').value + props.onChange(recipient) + if (recipient.match(ensRE) === null) { return this.setState({ loadingEns: false, @@ -39,34 +42,13 @@ EnsInput.prototype.render = function () { this.setState({ loadingEns: true, }) - this.checkName() + this.checkName(recipient) }, }) return h('div', { - style: { width: '100%' }, + style: { width: '100%', position: 'relative' }, }, [ - h('input.large-input.send-screen-input', opts), - // The address book functionality. - h('datalist#addresses', - [ - // Corresponds to the addresses owned. - Object.keys(props.identities).map((key) => { - const identity = props.identities[key] - return h('option', { - value: identity.address, - label: identity.name, - key: identity.address, - }) - }), - // Corresponds to previously sent-to addresses. - props.addressBook.map((identity) => { - return h('option', { - value: identity.address, - label: identity.name, - key: identity.address, - }) - }), - ]), + h(ToAutoComplete, { ...opts }), this.ensIcon(), ]) } @@ -83,8 +65,7 @@ EnsInput.prototype.componentDidMount = function () { } } -EnsInput.prototype.lookupEnsName = function () { - const recipient = document.querySelector('input[name="address"]').value +EnsInput.prototype.lookupEnsName = function (recipient) { const { ensResolution } = this.state log.info(`ENS attempting to resolve name: ${recipient}`) @@ -130,8 +111,8 @@ EnsInput.prototype.ensIcon = function (recipient) { title: hoverText, style: { position: 'absolute', - padding: '9px', - transform: 'translatex(-40px)', + top: '16px', + left: '-25px', }, }, this.ensIconContents(recipient)) } diff --git a/ui/app/components/input-number.js b/ui/app/components/input-number.js index fd8c5c309..5600e35ee 100644 --- a/ui/app/components/input-number.js +++ b/ui/app/components/input-number.js @@ -55,6 +55,7 @@ InputNumber.prototype.render = function () { className: 'customize-gas-input', value, placeholder, + type: 'number', onInputChange: newValue => { this.setValue(newValue) }, diff --git a/ui/app/components/mini-account-panel.js b/ui/app/components/mini-account-panel.js deleted file mode 100644 index c09cf5b7a..000000000 --- a/ui/app/components/mini-account-panel.js +++ /dev/null @@ -1,74 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') -const Identicon = require('./identicon') - -module.exports = AccountPanel - - -inherits(AccountPanel, Component) -function AccountPanel () { - Component.call(this) -} - -AccountPanel.prototype.render = function () { - var props = this.props - var picOrder = props.picOrder || 'left' - const { imageSeed } = props - - return ( - - h('.identity-panel.flex-row.flex-left', { - style: { - cursor: props.onClick ? 'pointer' : undefined, - }, - onClick: props.onClick, - }, [ - - this.genIcon(imageSeed, picOrder), - - h('div.flex-column.flex-justify-center', { - style: { - lineHeight: '15px', - order: 2, - display: 'flex', - alignItems: picOrder === 'left' ? 'flex-begin' : 'flex-end', - }, - }, this.props.children), - ]) - ) -} - -AccountPanel.prototype.genIcon = function (seed, picOrder) { - const props = this.props - - // When there is no seed value, this is a contract creation. - // We then show the contract icon. - if (!seed) { - return h('.identicon-wrapper.flex-column.select-none', { - style: { - order: picOrder === 'left' ? 1 : 3, - }, - }, [ - h('i.fa.fa-file-text-o.fa-lg', { - style: { - fontSize: '42px', - transform: 'translate(0px, -16px)', - }, - }), - ]) - } - - // If there was a seed, we return an identicon for that address. - return h('.identicon-wrapper.flex-column.select-none', { - style: { - order: picOrder === 'left' ? 1 : 3, - }, - }, [ - h(Identicon, { - address: seed, - imageify: props.imageifyIdenticons, - }), - ]) -} - diff --git a/ui/app/components/modals/account-details-modal.js b/ui/app/components/modals/account-details-modal.js index 75f989e86..927d73482 100644 --- a/ui/app/components/modals/account-details-modal.js +++ b/ui/app/components/modals/account-details-modal.js @@ -63,12 +63,12 @@ AccountDetailsModal.prototype.render = function () { h('div.account-modal-divider'), - h('button.btn-clear.account-modal__button', { + h('button.btn-primary.account-modal__button', { onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }), }, t('etherscanView')), // Holding on redesign for Export Private Key functionality - h('button.btn-clear.account-modal__button', { + h('button.btn-primary.account-modal__button', { onClick: () => showExportPrivateKeyModal(), }, t('exportPrivateKey')), diff --git a/ui/app/components/modals/deposit-ether-modal.js b/ui/app/components/modals/deposit-ether-modal.js index b642513d7..2de1240fc 100644 --- a/ui/app/components/modals/deposit-ether-modal.js +++ b/ui/app/components/modals/deposit-ether-modal.js @@ -94,7 +94,7 @@ DepositEtherModal.prototype.renderRow = function ({ ]), !hideButton && h('div.deposit-ether-modal__buy-row__button', [ - h('button.deposit-ether-modal__deposit-button', { + h('button.btn-primary--lg.deposit-ether-modal__deposit-button', { onClick: onButtonClick, }, [buttonLabel]), ]), diff --git a/ui/app/components/modals/export-private-key-modal.js b/ui/app/components/modals/export-private-key-modal.js index 017177cfd..cf42e4fa2 100644 --- a/ui/app/components/modals/export-private-key-modal.js +++ b/ui/app/components/modals/export-private-key-modal.js @@ -81,14 +81,14 @@ ExportPrivateKeyModal.prototype.renderButton = function (className, onClick, lab ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password, address, hideModal) { return h('div.export-private-key-buttons', {}, [ !privateKey && this.renderButton( - 'btn-cancel export-private-key__button export-private-key__button--cancel', + 'btn-secondary--lg export-private-key__button export-private-key__button--cancel', () => hideModal(), 'Cancel' ), (privateKey - ? this.renderButton('btn-clear export-private-key__button', () => hideModal(), t('done')) - : this.renderButton('btn-clear export-private-key__button', () => this.exportAccountAndGetPrivateKey(this.state.password, address), t('confirm')) + ? this.renderButton('btn-primary--lg export-private-key__button', () => hideModal(), t('done')) + : this.renderButton('btn-primary--lg export-private-key__button', () => this.exportAccountAndGetPrivateKey(this.state.password, address), t('confirm')) ), ]) diff --git a/ui/app/components/network-display.js b/ui/app/components/network-display.js new file mode 100644 index 000000000..9dc31b5c7 --- /dev/null +++ b/ui/app/components/network-display.js @@ -0,0 +1,51 @@ +const { Component } = require('react') +const h = require('react-hyperscript') +const PropTypes = require('prop-types') +const { connect } = require('react-redux') +const NetworkDropdownIcon = require('./dropdowns/components/network-dropdown-icon') +const t = require('../../i18n') + +const networkToColorHash = { + 1: '#038789', + 3: '#e91550', + 42: '#690496', + 4: '#ebb33f', +} + +class NetworkDisplay extends Component { + renderNetworkIcon () { + const { network } = this.props + const networkColor = networkToColorHash[network] + + return networkColor + ? h(NetworkDropdownIcon, { backgroundColor: networkColor }) + : h('i.fa.fa-question-circle.fa-med', { + style: { + margin: '0 4px', + color: 'rgb(125, 128, 130)', + }, + }) + } + + render () { + const { provider: { type } } = this.props + return h('.network-display__container', [ + this.renderNetworkIcon(), + h('.network-name', t(type)), + ]) + } +} + +NetworkDisplay.propTypes = { + network: PropTypes.string, + provider: PropTypes.object, +} + +const mapStateToProps = ({ metamask: { network, provider } }) => { + return { + network, + provider, + } +} + +module.exports = connect(mapStateToProps)(NetworkDisplay) diff --git a/ui/app/components/pending-personal-msg-details.js b/ui/app/components/pending-personal-msg-details.js deleted file mode 100644 index b896e9a7e..000000000 --- a/ui/app/components/pending-personal-msg-details.js +++ /dev/null @@ -1,60 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const t = require('../../i18n') - -const AccountPanel = require('./account-panel') -const BinaryRenderer = require('./binary-renderer') - -module.exports = PendingMsgDetails - -inherits(PendingMsgDetails, Component) -function PendingMsgDetails () { - Component.call(this) -} - -PendingMsgDetails.prototype.render = function () { - var state = this.props - var msgData = state.txData - - var msgParams = msgData.msgParams || {} - var address = msgParams.from || state.selectedAddress - var identity = state.identities[address] || { address: address } - var account = state.accounts[address] || { address: address } - - var { data } = msgParams - - return ( - h('div', { - key: msgData.id, - style: { - margin: '10px 20px', - }, - }, [ - - // account that will sign - h(AccountPanel, { - showFullAddress: true, - identity: identity, - account: account, - imageifyIdenticons: state.imageifyIdenticons, - }), - - // message data - h('div', { - style: { - height: '260px', - }, - }, [ - h('label.font-small.allcaps', { style: { display: 'block' } }, t('message')), - h(BinaryRenderer, { - value: data, - style: { - height: '215px', - }, - }), - ]), - - ]) - ) -} diff --git a/ui/app/components/pending-tx/confirm-deploy-contract.js b/ui/app/components/pending-tx/confirm-deploy-contract.js index 4810bb515..b75f3a964 100644 --- a/ui/app/components/pending-tx/confirm-deploy-contract.js +++ b/ui/app/components/pending-tx/confirm-deploy-contract.js @@ -10,6 +10,7 @@ const hexToBn = require('../../../../app/scripts/lib/hex-to-bn') const { conversionUtil } = require('../../conversion-util') const t = require('../../../i18n') const SenderToRecipient = require('../sender-to-recipient') +const NetworkDisplay = require('../network-display') const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') @@ -244,9 +245,12 @@ class ConfirmDeployContract extends Component { return ( h('.page-container', [ h('.page-container__header', [ - h('.page-container__back-button', { - onClick: () => backToAccountDetail(selectedAddress), - }, t('back')), + h('.page-container__header-row', [ + h('span.page-container__back-button', { + onClick: () => backToAccountDetail(selectedAddress), + }, t('back')), + window.METAMASK_UI_TYPE === 'notification' && h(NetworkDisplay), + ]), h('.page-container__title', t('confirmContract')), h('.page-container__subtitle', t('pleaseReviewTransaction')), ]), diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index 908df3671..d1ce25cbf 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -4,12 +4,18 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const actions = require('../../actions') const clone = require('clone') -const Identicon = require('../identicon') const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN const hexToBn = require('../../../../app/scripts/lib/hex-to-bn') -const { conversionUtil, addCurrencies } = require('../../conversion-util') +const { + conversionUtil, + addCurrencies, + multiplyCurrencies, +} = require('../../conversion-util') +const GasFeeDisplay = require('../send/gas-fee-display-v2') const t = require('../../../i18n') +const SenderToRecipient = require('../sender-to-recipient') +const NetworkDisplay = require('../network-display') const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') @@ -44,6 +50,7 @@ function mapDispatchToProps (dispatch) { to, value: amount, } = txParams + dispatch(actions.updateSend({ gasLimit, gasPrice, @@ -56,6 +63,29 @@ function mapDispatchToProps (dispatch) { dispatch(actions.showSendPage()) }, cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })), + showCustomizeGasModal: (txMeta, sendGasLimit, sendGasPrice, sendGasTotal) => { + const { id, txParams, lastGasPrice } = txMeta + const { gas: txGasLimit, gasPrice: txGasPrice } = txParams + + let forceGasMin + if (lastGasPrice) { + forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, { + multiplicandBase: 16, + multiplierBase: 10, + toNumericBase: 'hex', + fromDenomination: 'WEI', + })) + } + + dispatch(actions.updateSend({ + gasLimit: sendGasLimit || txGasLimit, + gasPrice: sendGasPrice || txGasPrice, + editingTransactionId: id, + gasTotal: sendGasTotal, + forceGasMin, + })) + dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })) + }, } } @@ -140,6 +170,7 @@ ConfirmSendEther.prototype.getGasFee = function () { return { FIAT, ETH, + gasFeeInHex: txFeeBn.toString(16), } } @@ -147,7 +178,7 @@ ConfirmSendEther.prototype.getData = function () { const { identities } = this.props const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} - const { FIAT: gasFeeInFIAT, ETH: gasFeeInETH } = this.getGasFee() + const { FIAT: gasFeeInFIAT, ETH: gasFeeInETH, gasFeeInHex } = this.getGasFee() const { FIAT: amountInFIAT, ETH: amountInETH } = this.getAmount() const totalInFIAT = addCurrencies(gasFeeInFIAT, amountInFIAT, { @@ -175,11 +206,20 @@ ConfirmSendEther.prototype.getData = function () { amountInETH, totalInFIAT, totalInETH, + gasFeeInHex, } } ConfirmSendEther.prototype.render = function () { - const { editTransaction, currentCurrency, clearSend } = this.props + const { + editTransaction, + currentCurrency, + clearSend, + conversionRate, + currentCurrency: convertedCurrency, + showCustomizeGasModal, + send: { gasTotal, gasLimit: sendGasLimit, gasPrice: sendGasPrice }, + } = this.props const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} @@ -193,13 +233,17 @@ ConfirmSendEther.prototype.render = function () { name: toName, }, memo, - gasFeeInFIAT, - gasFeeInETH, + gasFeeInHex, amountInFIAT, totalInFIAT, totalInETH, } = this.getData() + const title = txMeta.lastGasPrice ? 'Reprice Transaction' : 'Confirm' + const subtitle = txMeta.lastGasPrice + ? 'Increase your gas fee to attempt to overwrite and speed up your transaction' + : 'Please review your transaction.' + // This is from the latest master // It handles some of the errors that we are not currently handling // Leaving as comments fo reference @@ -214,194 +258,181 @@ ConfirmSendEther.prototype.render = function () { this.inputs = [] return ( - h('div.confirm-screen-container.confirm-send-ether', [ - // Main Send token Card - h('div.page-container', [ - h('div.page-container__header', [ - h('button.confirm-screen-back-button', { + // Main Send token Card + h('.page-container', [ + h('.page-container__header', [ + h('.page-container__header-row', [ + h('span.page-container__back-button', { onClick: () => editTransaction(txMeta), + style: { + visibility: !txMeta.lastGasPrice ? 'initial' : 'hidden', + }, }, 'Edit'), - h('div.page-container__title', 'Confirm'), - h('div.page-container__subtitle', `Please review your transaction.`), + window.METAMASK_UI_TYPE === 'notification' && h(NetworkDisplay), + ]), + h('.page-container__title', title), + h('.page-container__subtitle', subtitle), + ]), + h('.page-container__content', [ + h(SenderToRecipient, { + senderName: fromName, + senderAddress: fromAddress, + recipientName: toName, + recipientAddress: txParams.to, + }), + + // h('h3.flex-center.confirm-screen-sending-to-message', { + // style: { + // textAlign: 'center', + // fontSize: '16px', + // }, + // }, [ + // `You're sending to Recipient ...${toAddress.slice(toAddress.length - 4)}`, + // ]), + + h('h3.flex-center.confirm-screen-send-amount', [`${amountInFIAT}`]), + h('h3.flex-center.confirm-screen-send-amount-currency', [ currentCurrency.toUpperCase() ]), + h('div.flex-center.confirm-memo-wrapper', [ + h('h3.confirm-screen-send-memo', [ memo ? `"${memo}"` : '' ]), ]), - h('.page-container__content', [ - h('div.flex-row.flex-center.confirm-screen-identicons', [ - h('div.confirm-screen-account-wrapper', [ - h( - Identicon, - { - address: fromAddress, - diameter: 60, - }, - ), - h('span.confirm-screen-account-name', fromName), - // h('span.confirm-screen-account-number', fromAddress.slice(fromAddress.length - 4)), - ]), - h('i.fa.fa-arrow-right.fa-lg'), - h('div.confirm-screen-account-wrapper', [ - h( - Identicon, - { - address: txParams.to, - diameter: 60, - }, - ), - h('span.confirm-screen-account-name', toName), - // h('span.confirm-screen-account-number', toAddress.slice(toAddress.length - 4)), - ]), - ]), - // h('h3.flex-center.confirm-screen-sending-to-message', { - // style: { - // textAlign: 'center', - // fontSize: '16px', - // }, - // }, [ - // `You're sending to Recipient ...${toAddress.slice(toAddress.length - 4)}`, - // ]), - - h('h3.flex-center.confirm-screen-send-amount', [`${amountInFIAT}`]), - h('h3.flex-center.confirm-screen-send-amount-currency', [ currentCurrency.toUpperCase() ]), - h('div.flex-center.confirm-memo-wrapper', [ - h('h3.confirm-screen-send-memo', [ memo ? `"${memo}"` : '' ]), + h('div.confirm-screen-rows', [ + h('section.flex-row.flex-center.confirm-screen-row', [ + h('span.confirm-screen-label.confirm-screen-section-column', [ t('from') ]), + h('div.confirm-screen-section-column', [ + h('div.confirm-screen-row-info', fromName), + h('div.confirm-screen-row-detail', `...${fromAddress.slice(fromAddress.length - 4)}`), + ]), ]), - h('div.confirm-screen-rows', [ - h('section.flex-row.flex-center.confirm-screen-row', [ - h('span.confirm-screen-label.confirm-screen-section-column', [ t('from') ]), - h('div.confirm-screen-section-column', [ - h('div.confirm-screen-row-info', fromName), - h('div.confirm-screen-row-detail', `...${fromAddress.slice(fromAddress.length - 4)}`), - ]), + h('section.flex-row.flex-center.confirm-screen-row', [ + h('span.confirm-screen-label.confirm-screen-section-column', [ t('to') ]), + h('div.confirm-screen-section-column', [ + h('div.confirm-screen-row-info', toName), + h('div.confirm-screen-row-detail', `...${toAddress.slice(toAddress.length - 4)}`), ]), + ]), - h('section.flex-row.flex-center.confirm-screen-row', [ - h('span.confirm-screen-label.confirm-screen-section-column', [ t('to') ]), - h('div.confirm-screen-section-column', [ - h('div.confirm-screen-row-info', toName), - h('div.confirm-screen-row-detail', `...${toAddress.slice(toAddress.length - 4)}`), - ]), + h('section.flex-row.flex-center.confirm-screen-row', [ + h('span.confirm-screen-label.confirm-screen-section-column', [ t('gasFee') ]), + h('div.confirm-screen-section-column', [ + h(GasFeeDisplay, { + gasTotal: gasTotal || gasFeeInHex, + conversionRate, + convertedCurrency, + onClick: () => showCustomizeGasModal(txMeta, sendGasLimit, sendGasPrice, gasTotal), + }), ]), + ]), - h('section.flex-row.flex-center.confirm-screen-row', [ - h('span.confirm-screen-label.confirm-screen-section-column', [ t('gasFee') ]), - h('div.confirm-screen-section-column', [ - h('div.confirm-screen-row-info', `${gasFeeInFIAT} ${currentCurrency.toUpperCase()}`), - - h('div.confirm-screen-row-detail', `${gasFeeInETH} ETH`), - ]), + h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [ + h('div.confirm-screen-section-column', [ + h('span.confirm-screen-label', [ t('total') + ' ' ]), + h('div.confirm-screen-total-box__subtitle', [ t('amountPlusGas') ]), ]), - - h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [ - h('div.confirm-screen-section-column', [ - h('span.confirm-screen-label', [ t('total') + ' ' ]), - h('div.confirm-screen-total-box__subtitle', [ t('amountPlusGas') ]), - ]), - - h('div.confirm-screen-section-column', [ - h('div.confirm-screen-row-info', `${totalInFIAT} ${currentCurrency.toUpperCase()}`), - h('div.confirm-screen-row-detail', `${totalInETH} ETH`), - ]), + h('div.confirm-screen-section-column', [ + h('div.confirm-screen-row-info', `${totalInFIAT} ${currentCurrency.toUpperCase()}`), + h('div.confirm-screen-row-detail', `${totalInETH} ETH`), ]), ]), - - // These are latest errors handling from master - // Leaving as comments as reference when we start implementing error handling - // h('style', ` - // .conf-buttons button { - // margin-left: 10px; - // text-transform: uppercase; - // } - // `), - - // txMeta.simulationFails ? - // h('.error', { - // style: { - // marginLeft: 50, - // fontSize: '0.9em', - // }, - // }, 'Transaction Error. Exception thrown in contract code.') - // : null, - - // !isValidAddress ? - // h('.error', { - // style: { - // marginLeft: 50, - // fontSize: '0.9em', - // }, - // }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.') - // : null, - - // insufficientBalance ? - // h('span.error', { - // style: { - // marginLeft: 50, - // fontSize: '0.9em', - // }, - // }, 'Insufficient balance for transaction') - // : null, - - // // send + cancel - // h('.flex-row.flex-space-around.conf-buttons', { - // style: { - // display: 'flex', - // justifyContent: 'flex-end', - // margin: '14px 25px', - // }, - // }, [ - // h('button', { - // onClick: (event) => { - // this.resetGasFields() - // event.preventDefault() - // }, - // }, 'Reset'), - - // // Accept Button or Buy Button - // insufficientBalance ? h('button.btn-green', { onClick: props.buyEth }, 'Buy Ether') : - // h('input.confirm.btn-green', { - // type: 'submit', - // value: 'SUBMIT', - // style: { marginLeft: '10px' }, - // disabled: buyDisabled, - // }), - - // h('button.cancel.btn-red', { - // onClick: props.cancelTransaction, - // }, 'Reject'), - // ]), - // showRejectAll ? h('.flex-row.flex-space-around.conf-buttons', { - // style: { - // display: 'flex', - // justifyContent: 'flex-end', - // margin: '14px 25px', - // }, - // }, [ - // h('button.cancel.btn-red', { - // onClick: props.cancelAllTransactions, - // }, 'Reject All'), - // ]) : null, - // ]), - // ]) - // ) - // } ]), - h('form#pending-tx-form', { - onSubmit: this.onSubmit, - }, [ - h('.page-container__footer', [ - // Cancel Button - h('button.btn-cancel.page-container__footer-button.allcaps', { - onClick: (event) => { - clearSend() - this.cancel(event, txMeta) - }, - }, t('cancel')), - - // Accept Button - h('button.btn-confirm.page-container__footer-button.allcaps', [t('confirm')]), - ]), +// These are latest errors handling from master +// Leaving as comments as reference when we start implementing error handling +// h('style', ` +// .conf-buttons button { +// margin-left: 10px; +// text-transform: uppercase; +// } +// `), + +// txMeta.simulationFails ? +// h('.error', { +// style: { +// marginLeft: 50, +// fontSize: '0.9em', +// }, +// }, 'Transaction Error. Exception thrown in contract code.') +// : null, + +// !isValidAddress ? +// h('.error', { +// style: { +// marginLeft: 50, +// fontSize: '0.9em', +// }, +// }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.') +// : null, + +// insufficientBalance ? +// h('span.error', { +// style: { +// marginLeft: 50, +// fontSize: '0.9em', +// }, +// }, 'Insufficient balance for transaction') +// : null, + +// // send + cancel +// h('.flex-row.flex-space-around.conf-buttons', { +// style: { +// display: 'flex', +// justifyContent: 'flex-end', +// margin: '14px 25px', +// }, +// }, [ +// h('button', { +// onClick: (event) => { +// this.resetGasFields() +// event.preventDefault() +// }, +// }, 'Reset'), + +// // Accept Button or Buy Button +// insufficientBalance ? h('button.btn-green', { onClick: props.buyEth }, 'Buy Ether') : +// h('input.confirm.btn-green', { +// type: 'submit', +// value: 'SUBMIT', +// style: { marginLeft: '10px' }, +// disabled: buyDisabled, +// }), + +// h('button.cancel.btn-red', { +// onClick: props.cancelTransaction, +// }, 'Reject'), +// ]), +// showRejectAll ? h('.flex-row.flex-space-around.conf-buttons', { +// style: { +// display: 'flex', +// justifyContent: 'flex-end', +// margin: '14px 25px', +// }, +// }, [ +// h('button.cancel.btn-red', { +// onClick: props.cancelAllTransactions, +// }, 'Reject All'), +// ]) : null, +// ]), +// ]) +// ) +// } + ]), + + h('form#pending-tx-form', { + onSubmit: this.onSubmit, + }, [ + h('.page-container__footer', [ + // Cancel Button + h('button.btn-cancel.page-container__footer-button.allcaps', { + onClick: (event) => { + clearSend() + this.cancel(event, txMeta) + }, + }, t('cancel')), + + // Accept Button + h('button.btn-confirm.page-container__footer-button.allcaps', [t('confirm')]), ]), ]), ]) @@ -450,6 +481,27 @@ ConfirmSendEther.prototype.gatherTxMeta = function () { const state = this.state const txData = clone(state.txData) || clone(props.txData) + const { gasPrice: sendGasPrice, gas: sendGasLimit } = props.send + const { + lastGasPrice, + txParams: { + gasPrice: txGasPrice, + gas: txGasLimit, + }, + } = txData + + let forceGasMin + if (lastGasPrice) { + forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, { + multiplicandBase: 16, + multiplierBase: 10, + toNumericBase: 'hex', + })) + } + + txData.txParams.gasPrice = sendGasPrice || forceGasMin || txGasPrice + txData.txParams.gas = sendGasLimit || txGasLimit + // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) return txData } diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index 0a4182014..f9276e8a5 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -9,6 +9,7 @@ const actions = require('../../actions') const t = require('../../../i18n') const clone = require('clone') const Identicon = require('../identicon') +const GasFeeDisplay = require('../send/gas-fee-display-v2.js') const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN const { @@ -86,9 +87,43 @@ function mapDispatchToProps (dispatch, ownProps) { amount: tokenAmountInHex, errors: { to: null, amount: null }, editingTransactionId: id, + token: ownProps.token, })) dispatch(actions.showSendTokenPage()) }, + showCustomizeGasModal: (txMeta, sendGasLimit, sendGasPrice, sendGasTotal) => { + const { id, txParams, lastGasPrice } = txMeta + const { gas: txGasLimit, gasPrice: txGasPrice } = txParams + const tokenData = txParams.data && abiDecoder.decodeMethod(txParams.data) + const { params = [] } = tokenData + const { value: to } = params[0] || {} + const { value: tokenAmountInDec } = params[1] || {} + const tokenAmountInHex = conversionUtil(tokenAmountInDec, { + fromNumericBase: 'dec', + toNumericBase: 'hex', + }) + + let forceGasMin + if (lastGasPrice) { + forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, { + multiplicandBase: 16, + multiplierBase: 10, + toNumericBase: 'hex', + fromDenomination: 'WEI', + })) + } + + dispatch(actions.updateSend({ + gasLimit: sendGasLimit || txGasLimit, + gasPrice: sendGasPrice || txGasPrice, + editingTransactionId: id, + gasTotal: sendGasTotal, + to, + amount: tokenAmountInHex, + forceGasMin, + })) + dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })) + }, } } @@ -188,6 +223,7 @@ ConfirmSendToken.prototype.getGasFee = function () { token: tokenExchangeRate ? tokenGas : null, + gasFeeInHex: gasTotal.toString(16), } } @@ -240,19 +276,25 @@ ConfirmSendToken.prototype.renderHeroAmount = function () { } ConfirmSendToken.prototype.renderGasFee = function () { - const { token: { symbol }, currentCurrency } = this.props - const { fiat: fiatGas, token: tokenGas, eth: ethGas } = this.getGasFee() + const { + currentCurrency: convertedCurrency, + conversionRate, + send: { gasTotal, gasLimit: sendGasLimit, gasPrice: sendGasPrice }, + showCustomizeGasModal, + } = this.props + const txMeta = this.gatherTxMeta() + const { gasFeeInHex } = this.getGasFee() return ( h('section.flex-row.flex-center.confirm-screen-row', [ h('span.confirm-screen-label.confirm-screen-section-column', [ t('gasFee') ]), h('div.confirm-screen-section-column', [ - h('div.confirm-screen-row-info', `${fiatGas} ${currentCurrency}`), - - h( - 'div.confirm-screen-row-detail', - tokenGas ? `${tokenGas} ${symbol}` : `${ethGas} ETH` - ), + h(GasFeeDisplay, { + gasTotal: gasTotal || gasFeeInHex, + conversionRate, + convertedCurrency, + onClick: () => showCustomizeGasModal(txMeta, sendGasLimit, sendGasPrice, gasTotal), + }), ]), ]) ) @@ -308,16 +350,21 @@ ConfirmSendToken.prototype.render = function () { this.inputs = [] + const title = txMeta.lastGasPrice ? 'Reprice Transaction' : t('confirm') + const subtitle = txMeta.lastGasPrice + ? 'Increase your gas fee to attempt to overwrite and speed up your transaction' + : t('pleaseReviewTransaction') + return ( h('div.confirm-screen-container.confirm-send-token', [ // Main Send token Card h('div.page-container', [ h('div.page-container__header', [ - h('button.confirm-screen-back-button', { + !txMeta.lastGasPrice && h('button.confirm-screen-back-button', { onClick: () => editTransaction(txMeta), }, t('edit')), - h('div.page-container__title', t('confirm')), - h('div.page-container__subtitle', t('pleaseReviewTransaction')), + h('div.page-container__title', title), + h('div.page-container__subtitle', subtitle), ]), h('.page-container__content', [ h('div.flex-row.flex-center.confirm-screen-identicons', [ @@ -441,6 +488,27 @@ ConfirmSendToken.prototype.gatherTxMeta = function () { const state = this.state const txData = clone(state.txData) || clone(props.txData) + const { gasPrice: sendGasPrice, gas: sendGasLimit } = props.send + const { + lastGasPrice, + txParams: { + gasPrice: txGasPrice, + gas: txGasLimit, + }, + } = txData + + let forceGasMin + if (lastGasPrice) { + forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, { + multiplicandBase: 16, + multiplierBase: 10, + toNumericBase: 'hex', + })) + } + + txData.txParams.gasPrice = sendGasPrice || forceGasMin || txGasPrice + txData.txParams.gas = sendGasLimit || txGasLimit + // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) return txData } diff --git a/ui/app/components/pending-tx/index.js b/ui/app/components/pending-tx/index.js index f4f6afb8f..9c0453a3b 100644 --- a/ui/app/components/pending-tx/index.js +++ b/ui/app/components/pending-tx/index.js @@ -64,13 +64,20 @@ PendingTx.prototype.componentWillMount = async function () { }) } - try { + // inspect tx data for supported special confirmation screens + let isTokenTransaction = false + if (txParams.data) { + const tokenData = abiDecoder.decodeMethod(txParams.data) + const { name: tokenMethodName } = tokenData || {} + isTokenTransaction = (tokenMethodName === 'transfer') + } + + if (isTokenTransaction) { const token = util.getContractAtAddress(txParams.to) const results = await Promise.all([ token.symbol(), token.decimals(), ]) - const [ symbol, decimals ] = results if (symbol[0] && decimals[0]) { @@ -83,11 +90,14 @@ PendingTx.prototype.componentWillMount = async function () { }) } else { this.setState({ - transactionType: TX_TYPES.SEND_ETHER, + transactionType: TX_TYPES.SEND_TOKEN, + tokenAddress: txParams.to, + tokenSymbol: null, + tokenDecimals: null, isFetching: false, }) } - } catch (e) { + } else { this.setState({ transactionType: TX_TYPES.SEND_ETHER, isFetching: false, diff --git a/ui/app/components/pending-typed-msg-details.js b/ui/app/components/pending-typed-msg-details.js deleted file mode 100644 index ae0a1171e..000000000 --- a/ui/app/components/pending-typed-msg-details.js +++ /dev/null @@ -1,60 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits - -const AccountPanel = require('./account-panel') -const TypedMessageRenderer = require('./typed-message-renderer') -const t = require('../../i18n') - -module.exports = PendingMsgDetails - -inherits(PendingMsgDetails, Component) -function PendingMsgDetails () { - Component.call(this) -} - -PendingMsgDetails.prototype.render = function () { - var state = this.props - var msgData = state.txData - - var msgParams = msgData.msgParams || {} - var address = msgParams.from || state.selectedAddress - var identity = state.identities[address] || { address: address } - var account = state.accounts[address] || { address: address } - - var { data } = msgParams - - return ( - h('div', { - key: msgData.id, - style: { - margin: '10px 20px', - }, - }, [ - - // account that will sign - h(AccountPanel, { - showFullAddress: true, - identity: identity, - account: account, - imageifyIdenticons: state.imageifyIdenticons, - }), - - // message data - h('div', { - style: { - height: '260px', - }, - }, [ - h('label.font-small.allcaps', { style: { display: 'block' } }, t('youSign')), - h(TypedMessageRenderer, { - value: data, - style: { - height: '215px', - }, - }), - ]), - - ]) - ) -} diff --git a/ui/app/components/pending-typed-msg.js b/ui/app/components/pending-typed-msg.js deleted file mode 100644 index ccde5e8af..000000000 --- a/ui/app/components/pending-typed-msg.js +++ /dev/null @@ -1,47 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const PendingTxDetails = require('./pending-typed-msg-details') -const t = require('../../i18n') - -module.exports = PendingMsg - -inherits(PendingMsg, Component) -function PendingMsg () { - Component.call(this) -} - -PendingMsg.prototype.render = function () { - var state = this.props - var msgData = state.txData - - return ( - - h('div', { - key: msgData.id, - }, [ - - // header - h('h3', { - style: { - fontWeight: 'bold', - textAlign: 'center', - }, - }, t('signMessage')), - - // message details - h(PendingTxDetails, state), - - // sign + cancel - h('.flex-row.flex-space-around', [ - h('button.allcaps', { - onClick: state.cancelTypedMessage, - }, t('cancel')), - h('button.allcaps', { - onClick: state.signTypedMessage, - }, t('sign')), - ]), - ]) - - ) -} diff --git a/ui/app/components/range-slider.js b/ui/app/components/range-slider.js deleted file mode 100644 index 823f5eb01..000000000 --- a/ui/app/components/range-slider.js +++ /dev/null @@ -1,58 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits - -module.exports = RangeSlider - -inherits(RangeSlider, Component) -function RangeSlider () { - Component.call(this) -} - -RangeSlider.prototype.render = function () { - const state = this.state || {} - const props = this.props - const onInput = props.onInput || function () {} - const name = props.name - const { - min = 0, - max = 100, - increment = 1, - defaultValue = 50, - mirrorInput = false, - } = this.props.options - const {container, input, range} = props.style - - return ( - h('.flex-row', { - style: container, - }, [ - h('input', { - type: 'range', - name: name, - min: min, - max: max, - step: increment, - style: range, - value: state.value || defaultValue, - onChange: mirrorInput ? this.mirrorInputs.bind(this, event) : onInput, - }), - - // Mirrored input for range - mirrorInput ? h('input.large-input', { - type: 'number', - name: `${name}Mirror`, - min: min, - max: max, - value: state.value || defaultValue, - step: increment, - style: input, - onChange: this.mirrorInputs.bind(this, event), - }) : null, - ]) - ) -} - -RangeSlider.prototype.mirrorInputs = function (event) { - this.setState({value: event.target.value}) -} diff --git a/ui/app/components/send-token/index.js b/ui/app/components/send-token/index.js deleted file mode 100644 index 58743b641..000000000 --- a/ui/app/components/send-token/index.js +++ /dev/null @@ -1,440 +0,0 @@ -const Component = require('react').Component -const connect = require('react-redux').connect -const h = require('react-hyperscript') -const classnames = require('classnames') -const abi = require('ethereumjs-abi') -const inherits = require('util').inherits -const actions = require('../../actions') -const selectors = require('../../selectors') -const { isValidAddress, allNull } = require('../../util') -const t = require('../../../i18n') - -// const BalanceComponent = require('./balance-component') -const Identicon = require('../identicon') -const TokenBalance = require('../token-balance') -const CurrencyToggle = require('../send/currency-toggle') -const GasTooltip = require('../send/gas-tooltip') -const GasFeeDisplay = require('../send/gas-fee-display') - -module.exports = connect(mapStateToProps, mapDispatchToProps)(SendTokenScreen) - -function mapStateToProps (state) { - // const sidebarOpen = state.appState.sidebarOpen - - const { warning } = state.appState - const identities = state.metamask.identities - const addressBook = state.metamask.addressBook - const conversionRate = state.metamask.conversionRate - const currentBlockGasLimit = state.metamask.currentBlockGasLimit - const accounts = state.metamask.accounts - const selectedTokenAddress = state.metamask.selectedTokenAddress - const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0] - const selectedToken = selectors.getSelectedToken(state) - const tokenExchangeRates = state.metamask.tokenExchangeRates - const pair = `${selectedToken.symbol.toLowerCase()}_eth` - const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {} - - return { - selectedAddress, - selectedTokenAddress, - identities, - addressBook, - conversionRate, - tokenExchangeRate, - currentBlockGasLimit, - selectedToken, - warning, - } -} - -function mapDispatchToProps (dispatch) { - return { - backToAccountDetail: address => dispatch(actions.backToAccountDetail(address)), - hideWarning: () => dispatch(actions.hideWarning()), - addToAddressBook: (recipient, nickname) => dispatch( - actions.addToAddressBook(recipient, nickname) - ), - signTx: txParams => dispatch(actions.signTx(txParams)), - signTokenTx: (tokenAddress, toAddress, amount, txData) => ( - dispatch(actions.signTokenTx(tokenAddress, toAddress, amount, txData)) - ), - updateTokenExchangeRate: token => dispatch(actions.updateTokenExchangeRate(token)), - estimateGas: params => dispatch(actions.estimateGas(params)), - getGasPrice: () => dispatch(actions.getGasPrice()), - } -} - -inherits(SendTokenScreen, Component) -function SendTokenScreen () { - Component.call(this) - this.state = { - to: '', - amount: '0x0', - amountToSend: '0x0', - selectedCurrency: 'USD', - isGasTooltipOpen: false, - gasPrice: null, - gasLimit: null, - errors: {}, - } -} - -SendTokenScreen.prototype.componentWillMount = function () { - const { - updateTokenExchangeRate, - selectedToken: { symbol }, - getGasPrice, - estimateGas, - selectedAddress, - } = this.props - - updateTokenExchangeRate(symbol) - - const data = Array.prototype.map.call( - abi.rawEncode(['address', 'uint256'], [selectedAddress, '0x0']), - x => ('00' + x.toString(16)).slice(-2) - ).join('') - - console.log(data) - Promise.all([ - getGasPrice(), - estimateGas({ - from: selectedAddress, - value: '0x0', - gas: '746a528800', - data, - }), - ]) - .then(([blockGasPrice, estimatedGas]) => { - console.log({ blockGasPrice, estimatedGas}) - this.setState({ - gasPrice: blockGasPrice, - gasLimit: estimatedGas, - }) - }) -} - -SendTokenScreen.prototype.validate = function () { - const { - to, - amount: stringAmount, - gasPrice: hexGasPrice, - gasLimit: hexGasLimit, - } = this.state - - const gasPrice = parseInt(hexGasPrice, 16) - const gasLimit = parseInt(hexGasLimit, 16) / 1000000000 - const amount = Number(stringAmount) - - const errors = { - to: !to ? t('required') : null, - amount: !amount ? t('required') : null, - gasPrice: !gasPrice ? t('gasPriceRequired') : null, - gasLimit: !gasLimit ? t('gasLimitRequired') : null, - } - - if (to && !isValidAddress(to)) { - errors.to = t('invalidAddress') - } - - const isValid = Object.entries(errors).every(([key, value]) => value === null) - return { - isValid, - errors: isValid ? {} : errors, - } -} - -SendTokenScreen.prototype.setErrorsFor = function (field) { - const { errors: previousErrors } = this.state - - const { - isValid, - errors: newErrors, - } = this.validate() - - const nextErrors = Object.assign({}, previousErrors, { - [field]: newErrors[field] || null, - }) - - if (!isValid) { - this.setState({ - errors: nextErrors, - isValid, - }) - } -} - -SendTokenScreen.prototype.clearErrorsFor = function (field) { - const { errors: previousErrors } = this.state - const nextErrors = Object.assign({}, previousErrors, { - [field]: null, - }) - - this.setState({ - errors: nextErrors, - isValid: allNull(nextErrors), - }) -} - -SendTokenScreen.prototype.getAmountToSend = function (amount, selectedToken) { - const { decimals } = selectedToken || {} - const multiplier = Math.pow(10, Number(decimals || 0)) - const sendAmount = '0x' + Number(amount * multiplier).toString(16) - return sendAmount -} - -SendTokenScreen.prototype.submit = function () { - const { - to, - amount, - gasPrice, - gasLimit, - } = this.state - - const { - identities, - selectedAddress, - selectedTokenAddress, - hideWarning, - addToAddressBook, - signTokenTx, - selectedToken, - } = this.props - - const { nickname = ' ' } = identities[to] || {} - - hideWarning() - addToAddressBook(to, nickname) - - const txParams = { - from: selectedAddress, - value: '0', - gas: gasLimit, - gasPrice: gasPrice, - } - - const sendAmount = this.getAmountToSend(amount, selectedToken) - - signTokenTx(selectedTokenAddress, to, sendAmount, txParams) -} - -SendTokenScreen.prototype.renderToAddressInput = function () { - const { - identities, - addressBook, - } = this.props - - const { - to, - errors: { to: errorMessage }, - } = this.state - - return h('div', { - className: classnames('send-screen-input-wrapper', { - 'send-screen-input-wrapper--error': errorMessage, - }), - }, [ - h('div', [t('to') + ':']), - h('input.large-input.send-screen-input', { - name: 'address', - list: 'addresses', - placeholder: t('address'), - value: to, - onChange: e => this.setState({ - to: e.target.value, - errors: {}, - }), - onBlur: () => { - this.setErrorsFor('to') - }, - onFocus: event => { - if (to) event.target.select() - this.clearErrorsFor('to') - }, - }), - h('datalist#addresses', [ - // Corresponds to the addresses owned. - Object.entries(identities).map(([key, { address, name }]) => { - return h('option', { - value: address, - label: name, - key: address, - }) - }), - addressBook.map(({ address, name }) => { - return h('option', { - value: address, - label: name, - key: address, - }) - }), - ]), - h('div.send-screen-input-wrapper__error-message', [ errorMessage ]), - ]) -} - -SendTokenScreen.prototype.renderAmountInput = function () { - const { - selectedCurrency, - amount, - errors: { amount: errorMessage }, - } = this.state - - const { - tokenExchangeRate, - selectedToken: {symbol}, - } = this.props - - return h('div.send-screen-input-wrapper', { - className: classnames('send-screen-input-wrapper', { - 'send-screen-input-wrapper--error': errorMessage, - }), - }, [ - h('div.send-screen-amount-labels', [ - h('span', [t('amount')]), - h(CurrencyToggle, { - currentCurrency: tokenExchangeRate ? selectedCurrency : 'USD', - currencies: tokenExchangeRate ? [ symbol, 'USD' ] : [], - onClick: currency => this.setState({ selectedCurrency: currency }), - }), - ]), - h('input.large-input.send-screen-input', { - placeholder: `0 ${symbol}`, - type: 'number', - value: amount, - onChange: e => this.setState({ - amount: e.target.value, - }), - onBlur: () => { - this.setErrorsFor('amount') - }, - onFocus: () => this.clearErrorsFor('amount'), - }), - h('div.send-screen-input-wrapper__error-message', [ errorMessage ]), - ]) -} - -SendTokenScreen.prototype.renderGasInput = function () { - const { - isGasTooltipOpen, - gasPrice, - gasLimit, - selectedCurrency, - errors: { - gasPrice: gasPriceErrorMessage, - gasLimit: gasLimitErrorMessage, - }, - } = this.state - - const { - conversionRate, - tokenExchangeRate, - currentBlockGasLimit, - } = this.props - - return h('div.send-screen-input-wrapper', { - className: classnames('send-screen-input-wrapper', { - 'send-screen-input-wrapper--error': gasPriceErrorMessage || gasLimitErrorMessage, - }), - }, [ - isGasTooltipOpen && h(GasTooltip, { - className: 'send-tooltip', - gasPrice: gasPrice || '0x0', - gasLimit: gasLimit || '0x0', - onClose: () => this.setState({ isGasTooltipOpen: false }), - onFeeChange: ({ gasLimit, gasPrice }) => { - this.setState({ gasLimit, gasPrice, errors: {} }) - }, - onBlur: () => { - this.setErrorsFor('gasLimit') - this.setErrorsFor('gasPrice') - }, - onFocus: () => { - this.clearErrorsFor('gasLimit') - this.clearErrorsFor('gasPrice') - }, - }), - - h('div.send-screen-gas-labels', {}, [ - h('span', [ h('i.fa.fa-bolt'), t('gasFee') + ':']), - h('span', [t('whatsThis')]), - ]), - h('div.large-input.send-screen-gas-input', [ - h(GasFeeDisplay, { - conversionRate, - tokenExchangeRate, - gasPrice: gasPrice || '0x0', - activeCurrency: selectedCurrency, - gas: gasLimit || '0x0', - blockGasLimit: currentBlockGasLimit, - }), - h( - 'div.send-screen-gas-input-customize', - { onClick: () => this.setState({ isGasTooltipOpen: !isGasTooltipOpen }) }, - [t('customize')] - ), - ]), - h('div.send-screen-input-wrapper__error-message', [ - gasPriceErrorMessage || gasLimitErrorMessage, - ]), - ]) -} - -SendTokenScreen.prototype.renderMemoInput = function () { - return h('div.send-screen-input-wrapper', [ - h('div', {}, [t('transactionMemo')]), - h( - 'input.large-input.send-screen-input', - { onChange: e => this.setState({ memo: e.target.value }) } - ), - ]) -} - -SendTokenScreen.prototype.renderButtons = function () { - const { selectedAddress, backToAccountDetail } = this.props - const { isValid } = this.validate() - - return h('div.send-token__button-group', [ - h('button.send-token__button-next.btn-secondary', { - className: !isValid && 'send-screen__send-button__disabled', - onClick: () => isValid && this.submit(), - }, [t('next')]), - h('button.send-token__button-cancel.btn-tertiary', { - onClick: () => backToAccountDetail(selectedAddress), - }, [t('cancel')]), - ]) -} - -SendTokenScreen.prototype.render = function () { - const { - selectedTokenAddress, - selectedToken, - warning, - } = this.props - - return h('div.send-token', [ - h('div.send-token__content', [ - h(Identicon, { - diameter: 75, - address: selectedTokenAddress, - }), - h('div.send-token__title', [t('sendTokens')]), - h('div.send-token__description', [t('sendTokensAnywhere')]), - h('div.send-token__balance-text', [t('tokenBalance')]), - h('div.send-token__token-balance', [ - h(TokenBalance, { token: selectedToken, balanceOnly: true }), - ]), - h('div.send-token__token-symbol', [selectedToken.symbol]), - this.renderToAddressInput(), - this.renderAmountInput(), - this.renderGasInput(), - this.renderMemoInput(), - warning && h('div.send-screen-input-wrapper--error', {}, - h('div.send-screen-input-wrapper__error-message', [ - warning, - ]) - ), - ]), - this.renderButtons(), - ]) -} diff --git a/ui/app/components/send/currency-toggle.js b/ui/app/components/send/currency-toggle.js deleted file mode 100644 index 7aaccd490..000000000 --- a/ui/app/components/send/currency-toggle.js +++ /dev/null @@ -1,44 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const classnames = require('classnames') - -module.exports = CurrencyToggle - -inherits(CurrencyToggle, Component) -function CurrencyToggle () { - Component.call(this) -} - -const defaultCurrencies = [ 'ETH', 'USD' ] - -CurrencyToggle.prototype.renderToggles = function () { - const { onClick, activeCurrency } = this.props - const [currencyA, currencyB] = this.props.currencies || defaultCurrencies - - return [ - h('span', { - className: classnames('currency-toggle__item', { - 'currency-toggle__item--selected': currencyA === activeCurrency, - }), - onClick: () => onClick(currencyA), - }, [ currencyA ]), - '<>', - h('span', { - className: classnames('currency-toggle__item', { - 'currency-toggle__item--selected': currencyB === activeCurrency, - }), - onClick: () => onClick(currencyB), - }, [ currencyB ]), - ] -} - -CurrencyToggle.prototype.render = function () { - const currencies = this.props.currencies || defaultCurrencies - - return h('span.currency-toggle', currencies.length - ? this.renderToggles() - : [] - ) -} - diff --git a/ui/app/components/send/eth-fee-display.js b/ui/app/components/send/eth-fee-display.js deleted file mode 100644 index 9eda5ec62..000000000 --- a/ui/app/components/send/eth-fee-display.js +++ /dev/null @@ -1,37 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const EthBalance = require('../eth-balance') -const { getTxFeeBn } = require('../../util') - -module.exports = EthFeeDisplay - -inherits(EthFeeDisplay, Component) -function EthFeeDisplay () { - Component.call(this) -} - -EthFeeDisplay.prototype.render = function () { - const { - activeCurrency, - conversionRate, - gas, - gasPrice, - blockGasLimit, - } = this.props - - return h(EthBalance, { - value: getTxFeeBn(gas, gasPrice, blockGasLimit), - currentCurrency: activeCurrency, - conversionRate, - showFiat: false, - hideTooltip: true, - styleOveride: { - color: '#5d5d5d', - fontSize: '16px', - fontFamily: 'DIN OT', - lineHeight: '22.4px', - }, - }) -} - diff --git a/ui/app/components/send/gas-fee-display-v2.js b/ui/app/components/send/gas-fee-display-v2.js index 0c6f76303..f6af13454 100644 --- a/ui/app/components/send/gas-fee-display-v2.js +++ b/ui/app/components/send/gas-fee-display-v2.js @@ -18,6 +18,7 @@ GasFeeDisplay.prototype.render = function () { onClick, primaryCurrency = 'ETH', convertedCurrency, + gasLoadingError, } = this.props return h('div.send-v2__gas-fee-display', [ @@ -31,13 +32,15 @@ GasFeeDisplay.prototype.render = function () { convertedPrefix: '$', readOnly: true, }) - : h('div.currency-display', t('loading')), + : gasLoadingError + ? h('div..currency-display.currency-display--message', 'Set with the gas price customizer.') + : h('div.currency-display', t('loading')), - h('button.send-v2__sliders-icon-container', { + h('button.sliders-icon-container', { onClick, - disabled: !gasTotal, + disabled: !gasTotal && !gasLoadingError, }, [ - h('i.fa.fa-sliders.send-v2__sliders-icon'), + h('i.fa.fa-sliders.sliders-icon'), ]), ]) diff --git a/ui/app/components/send/gas-fee-display.js b/ui/app/components/send/gas-fee-display.js deleted file mode 100644 index a9a3f3f49..000000000 --- a/ui/app/components/send/gas-fee-display.js +++ /dev/null @@ -1,62 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const USDFeeDisplay = require('./usd-fee-display') -const EthFeeDisplay = require('./eth-fee-display') -const { getTxFeeBn, formatBalance, shortenBalance } = require('../../util') - -module.exports = GasFeeDisplay - -inherits(GasFeeDisplay, Component) -function GasFeeDisplay () { - Component.call(this) -} - -GasFeeDisplay.prototype.getTokenValue = function () { - const { - tokenExchangeRate, - gas, - gasPrice, - blockGasLimit, - } = this.props - - const value = formatBalance(getTxFeeBn(gas, gasPrice, blockGasLimit), 6, true) - const [ethNumber] = value.split(' ') - - return shortenBalance(Number(ethNumber) / tokenExchangeRate, 6) -} - -GasFeeDisplay.prototype.render = function () { - const { - activeCurrency, - conversionRate, - gas, - gasPrice, - blockGasLimit, - } = this.props - - switch (activeCurrency) { - case 'USD': - return h(USDFeeDisplay, { - activeCurrency, - conversionRate, - gas, - gasPrice, - blockGasLimit, - }) - case 'ETH': - return h(EthFeeDisplay, { - activeCurrency, - conversionRate, - gas, - gasPrice, - blockGasLimit, - }) - default: - return h('div.token-gas', [ - h('div.token-gas__amount', this.getTokenValue()), - h('div.token-gas__symbol', activeCurrency), - ]) - } -} - diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js index 1106902b7..08c26a91f 100644 --- a/ui/app/components/send/send-v2-container.js +++ b/ui/app/components/send/send-v2-container.js @@ -48,6 +48,7 @@ function mapStateToProps (state) { primaryCurrency, convertedCurrency: getCurrentCurrency(state), data, + selectedAddress, amountConversionRate: selectedToken ? tokenToFiatRate : conversionRate, tokenContract: getSelectedTokenContract(state), unapprovedTxs: state.metamask.unapprovedTxs, @@ -68,13 +69,13 @@ function mapDispatchToProps (dispatch) { updateAndApproveTx: txParams => dispatch(actions.updateAndApproveTx(txParams)), updateTx: txData => dispatch(actions.updateTransaction(txData)), setSelectedAddress: address => dispatch(actions.setSelectedAddress(address)), - addToAddressBook: address => dispatch(actions.addToAddressBook(address)), + addToAddressBook: (address, nickname) => dispatch(actions.addToAddressBook(address, nickname)), updateGasTotal: newTotal => dispatch(actions.updateGasTotal(newTotal)), updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)), updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)), updateSendTokenBalance: tokenBalance => dispatch(actions.updateSendTokenBalance(tokenBalance)), updateSendFrom: newFrom => dispatch(actions.updateSendFrom(newFrom)), - updateSendTo: newTo => dispatch(actions.updateSendTo(newTo)), + updateSendTo: (newTo, nickname) => dispatch(actions.updateSendTo(newTo, nickname)), updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)), updateSendMemo: newMemo => dispatch(actions.updateSendMemo(newMemo)), updateSendErrors: newError => dispatch(actions.updateSendErrors(newError)), diff --git a/ui/app/components/send/usd-fee-display.js b/ui/app/components/send/usd-fee-display.js deleted file mode 100644 index 4cf31a493..000000000 --- a/ui/app/components/send/usd-fee-display.js +++ /dev/null @@ -1,35 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const FiatValue = require('../fiat-value') -const { getTxFeeBn } = require('../../util') - -module.exports = USDFeeDisplay - -inherits(USDFeeDisplay, Component) -function USDFeeDisplay () { - Component.call(this) -} - -USDFeeDisplay.prototype.render = function () { - const { - activeCurrency, - conversionRate, - gas, - gasPrice, - blockGasLimit, - } = this.props - - return h(FiatValue, { - value: getTxFeeBn(gas, gasPrice, blockGasLimit), - conversionRate, - currentCurrency: activeCurrency, - style: { - color: '#5d5d5d', - fontSize: '16px', - fontFamily: 'DIN OT', - lineHeight: '22.4px', - }, - }) -} - diff --git a/ui/app/components/sender-to-recipient.js b/ui/app/components/sender-to-recipient.js index 25b9c7d6b..f35c353ad 100644 --- a/ui/app/components/sender-to-recipient.js +++ b/ui/app/components/sender-to-recipient.js @@ -5,6 +5,28 @@ const t = require('../../i18n') const Identicon = require('./identicon') class SenderToRecipient extends Component { + renderRecipientIcon () { + const { recipientAddress } = this.props + return ( + recipientAddress + ? h(Identicon, { address: recipientAddress, diameter: 20 }) + : h('i.fa.fa-file-text-o') + ) + } + + renderRecipient () { + const { recipientName } = this.props + return ( + h('.sender-to-recipient__recipient', [ + this.renderRecipientIcon(), + h( + '.sender-to-recipient__name.sender-to-recipient__recipient-name', + recipientName || t('newContract') + ), + ]) + ) + } + render () { const { senderName, senderAddress } = this.props @@ -28,10 +50,7 @@ class SenderToRecipient extends Component { }), ]), ]), - h('.sender-to-recipient__recipient', [ - h('i.fa.fa-file-text-o'), - h('.sender-to-recipient__name.sender-to-recipient__recipient-name', t('newContract')), - ]), + this.renderRecipient(), ]) ) } @@ -40,6 +59,8 @@ class SenderToRecipient extends Component { SenderToRecipient.propTypes = { senderName: PropTypes.string, senderAddress: PropTypes.string, + recipientName: PropTypes.string, + recipientAddress: PropTypes.string, } module.exports = SenderToRecipient diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index 3f8c17932..5729f893c 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -236,7 +236,7 @@ ShapeshiftForm.prototype.render = function () { ]), - !depositAddress && h('button.shapeshift-form__shapeshift-buy-btn', { + !depositAddress && h('button.btn-primary--lg.shapeshift-form__shapeshift-buy-btn', { className: btnClass, disabled: !token, onClick: () => this.onBuyWithShapeShift(), diff --git a/ui/app/components/signature-request.js b/ui/app/components/signature-request.js index 7bf34e7b6..810a52e55 100644 --- a/ui/app/components/signature-request.js +++ b/ui/app/components/signature-request.js @@ -223,10 +223,10 @@ SignatureRequest.prototype.renderFooter = function () { } return h('div.request-signature__footer', [ - h('button.request-signature__footer__cancel-button', { + h('button.btn-secondary--lg.request-signature__footer__cancel-button', { onClick: cancel, }, t('cancel')), - h('button.request-signature__footer__sign-button', { + h('button.btn-primary--lg', { onClick: sign, }, t('sign')), ]) diff --git a/ui/app/components/template.js b/ui/app/components/template.js deleted file mode 100644 index b6ed8eaa0..000000000 --- a/ui/app/components/template.js +++ /dev/null @@ -1,18 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits - -module.exports = NewComponent - -inherits(NewComponent, Component) -function NewComponent () { - Component.call(this) -} - -NewComponent.prototype.render = function () { - const props = this.props - - return ( - h('span', props.message) - ) -} diff --git a/ui/app/components/transaction-list-item-icon.js b/ui/app/components/transaction-list-item-icon.js deleted file mode 100644 index f442b05af..000000000 --- a/ui/app/components/transaction-list-item-icon.js +++ /dev/null @@ -1,68 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const Tooltip = require('./tooltip') - -const Identicon = require('./identicon') - -module.exports = TransactionIcon - -inherits(TransactionIcon, Component) -function TransactionIcon () { - Component.call(this) -} - -TransactionIcon.prototype.render = function () { - const { transaction, txParams, isMsg } = this.props - switch (transaction.status) { - case 'unapproved': - return h(!isMsg ? '.unapproved-tx-icon' : 'i.fa.fa-certificate.fa-lg') - - case 'rejected': - return h('i.fa.fa-exclamation-triangle.fa-lg.warning', { - style: { - width: '24px', - }, - }) - - case 'failed': - return h('i.fa.fa-exclamation-triangle.fa-lg.error', { - style: { - width: '24px', - }, - }) - - case 'submitted': - return h(Tooltip, { - title: 'Pending', - position: 'right', - }, [ - h('i.fa.fa-ellipsis-h', { - style: { - fontSize: '27px', - }, - }), - ]) - } - - if (isMsg) { - return h('i.fa.fa-certificate.fa-lg', { - style: { - width: '24px', - }, - }) - } - - if (txParams.to) { - return h(Identicon, { - diameter: 24, - address: txParams.to || transaction.hash, - }) - } else { - return h('i.fa.fa-file-text-o.fa-lg', { - style: { - width: '24px', - }, - }) - } -} diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js deleted file mode 100644 index 6d6e79bd5..000000000 --- a/ui/app/components/transaction-list-item.js +++ /dev/null @@ -1,239 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const connect = require('react-redux').connect - -const EthBalance = require('./eth-balance') -const addressSummary = require('../util').addressSummary -const explorerLink = require('etherscan-link').createExplorerLink -const CopyButton = require('./copyButton') -const vreme = new (require('vreme'))() -const Tooltip = require('./tooltip') -const numberToBN = require('number-to-bn') -const actions = require('../actions') -const t = require('../../i18n') - -const TransactionIcon = require('./transaction-list-item-icon') -const ShiftListItem = require('./shift-list-item') - -const mapDispatchToProps = dispatch => { - return { - retryTransaction: transactionId => dispatch(actions.retryTransaction(transactionId)), - } -} - -module.exports = connect(null, mapDispatchToProps)(TransactionListItem) - -inherits(TransactionListItem, Component) -function TransactionListItem () { - Component.call(this) -} - -TransactionListItem.prototype.showRetryButton = function () { - const { transaction = {} } = this.props - const { status, time } = transaction - return status === 'submitted' && Date.now() - time > 30000 -} - -TransactionListItem.prototype.render = function () { - const { transaction, network, conversionRate, currentCurrency } = this.props - const { status } = transaction - if (transaction.key === 'shapeshift') { - if (network === '1') return h(ShiftListItem, transaction) - } - var date = formatDate(transaction.time) - - let isLinkable = false - const numericNet = parseInt(network) - isLinkable = numericNet === 1 || numericNet === 3 || numericNet === 4 || numericNet === 42 - - var isMsg = ('msgParams' in transaction) - var isTx = ('txParams' in transaction) - var isPending = status === 'unapproved' - let txParams - if (isTx) { - txParams = transaction.txParams - } else if (isMsg) { - txParams = transaction.msgParams - } - - const nonce = txParams.nonce ? numberToBN(txParams.nonce).toString(10) : '' - - const isClickable = ('hash' in transaction && isLinkable) || isPending - return ( - h('.transaction-list-item.flex-column', { - onClick: (event) => { - if (isPending) { - this.props.showTx(transaction.id) - } - event.stopPropagation() - if (!transaction.hash || !isLinkable) return - var url = explorerLink(transaction.hash, parseInt(network)) - global.platform.openWindow({ url }) - }, - style: { - padding: '20px 0', - alignItems: 'center', - }, - }, [ - h(`.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, { - style: { - width: '100%', - }, - }, [ - h('.identicon-wrapper.flex-column.flex-center.select-none', [ - h(TransactionIcon, { txParams, transaction, isTx, isMsg }), - ]), - - h(Tooltip, { - title: t('transactionNumber'), - position: 'right', - }, [ - h('span', { - style: { - display: 'flex', - cursor: 'normal', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - padding: '10px', - }, - }, nonce), - ]), - - h('.flex-column', {style: {width: '200px', overflow: 'hidden'}}, [ - domainField(txParams), - h('div', date), - recipientField(txParams, transaction, isTx, isMsg), - ]), - - // Places a copy button if tx is successful, else places a placeholder empty div. - transaction.hash ? h(CopyButton, { value: transaction.hash }) : h('div', {style: { display: 'flex', alignItems: 'center', width: '26px' }}), - - isTx ? h(EthBalance, { - value: txParams.value, - conversionRate, - currentCurrency, - width: '55px', - shorten: true, - showFiat: false, - style: {fontSize: '15px'}, - }) : h('.flex-column'), - ]), - - this.showRetryButton() && h('.transition-list-item__retry.grow-on-hover', { - onClick: event => { - event.stopPropagation() - this.resubmit() - }, - style: { - height: '22px', - borderRadius: '22px', - color: '#F9881B', - padding: '0 20px', - backgroundColor: '#FFE3C9', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - fontSize: '8px', - cursor: 'pointer', - }, - }, [ - h('div', { - style: { - paddingRight: '2px', - }, - }, t('takesTooLong')), - h('div', { - style: { - textDecoration: 'underline', - }, - }, t('retryWithMoreGas')), - ]), - ]) - ) -} - -TransactionListItem.prototype.resubmit = function () { - const { transaction } = this.props - this.props.retryTransaction(transaction.id) -} - -function domainField (txParams) { - return h('div', { - style: { - fontSize: 'x-small', - color: '#ABA9AA', - overflow: 'hidden', - textOverflow: 'ellipsis', - width: '100%', - }, - }, [ - txParams.origin, - ]) -} - -function recipientField (txParams, transaction, isTx, isMsg) { - let message - - if (isMsg) { - message = t('sigRequested') - } else if (txParams.to) { - message = addressSummary(txParams.to) - } else { - message = t('contractDeployment') - } - - return h('div', { - style: { - fontSize: 'x-small', - color: '#ABA9AA', - }, - }, [ - message, - renderErrorOrWarning(transaction), - ]) -} - -function formatDate (date) { - return vreme.format(new Date(date), 'March 16 2014 14:30') -} - -function renderErrorOrWarning (transaction) { - const { status } = transaction - - // show rejected - if (status === 'rejected') { - return h('span.error', ' (' + t('rejected') + ')') - } - if (transaction.err || transaction.warning) { - const { err, warning = {} } = transaction - const errFirst = !!((err && warning) || err) - - errFirst ? err.message : warning.message - - // show error - if (err) { - const message = err.message || '' - return ( - h(Tooltip, { - title: message, - position: 'bottom', - }, [ - h(`span.error`, ` (` + t('failed') + `)`), - ]) - ) - } - - // show warning - if (warning) { - const message = warning.message - return h(Tooltip, { - title: message, - position: 'bottom', - }, [ - h(`span.warning`, ` (` + t('warning') + `)`), - ]) - } - } -} diff --git a/ui/app/components/transaction-list.js b/ui/app/components/transaction-list.js deleted file mode 100644 index 07f7a06ae..000000000 --- a/ui/app/components/transaction-list.js +++ /dev/null @@ -1,87 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits - -const TransactionListItem = require('./transaction-list-item') -const t = require('../../i18n') - -module.exports = TransactionList - - -inherits(TransactionList, Component) -function TransactionList () { - Component.call(this) -} - -TransactionList.prototype.render = function () { - const { transactions, network, unapprovedMsgs, conversionRate } = this.props - - var shapeShiftTxList - if (network === '1') { - shapeShiftTxList = this.props.shapeShiftTxList - } - const txsToRender = !shapeShiftTxList ? transactions.concat(unapprovedMsgs) : transactions.concat(unapprovedMsgs, shapeShiftTxList) - .sort((a, b) => b.time - a.time) - - return ( - - h('section.transaction-list.full-flex-height', { - style: { - justifyContent: 'center', - }, - }, [ - - h('style', ` - .transaction-list .transaction-list-item:not(:last-of-type) { - border-bottom: 1px solid #D4D4D4; - } - .transaction-list .transaction-list-item .ether-balance-label { - display: block !important; - font-size: small; - } - `), - - h('.tx-list', { - style: { - overflowY: 'auto', - height: '100%', - padding: '0 20px', - textAlign: 'center', - }, - }, [ - - txsToRender.length - ? txsToRender.map((transaction, i) => { - let key - switch (transaction.key) { - case 'shapeshift': - const { depositAddress, time } = transaction - key = `shift-tx-${depositAddress}-${time}-${i}` - break - default: - key = `tx-${transaction.id}-${i}` - } - return h(TransactionListItem, { - transaction, i, network, key, - conversionRate, - showTx: (txId) => { - this.props.viewPendingTx(txId) - }, - }) - }) - : h('.flex-center.full-flex-height', { - style: { - flexDirection: 'column', - justifyContent: 'center', - }, - }, [ - h('p', { - style: { - marginTop: '50px', - }, - }, t('noTransactionHistory')), - ]), - ]), - ]) - ) -} diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js index 849d70489..d104eda88 100644 --- a/ui/app/components/tx-list-item.js +++ b/ui/app/components/tx-list-item.js @@ -9,19 +9,28 @@ abiDecoder.addABI(abi) const Identicon = require('./identicon') const contractMap = require('eth-contract-metadata') +const actions = require('../actions') const { conversionUtil, multiplyCurrencies } = require('../conversion-util') const { calcTokenAmount } = require('../token-util') const { getCurrentCurrency } = require('../selectors') const t = require('../../i18n') -module.exports = connect(mapStateToProps)(TxListItem) +module.exports = connect(mapStateToProps, mapDispatchToProps)(TxListItem) function mapStateToProps (state) { return { tokens: state.metamask.tokens, currentCurrency: getCurrentCurrency(state), tokenExchangeRates: state.metamask.tokenExchangeRates, + selectedAddressTxList: state.metamask.selectedAddressTxList, + } +} + +function mapDispatchToProps (dispatch) { + return { + setSelectedToken: tokenAddress => dispatch(actions.setSelectedToken(tokenAddress)), + retryTransaction: transactionId => dispatch(actions.retryTransaction(transactionId)), } } @@ -32,6 +41,7 @@ function TxListItem () { this.state = { total: null, fiatTotal: null, + isTokenTx: null, } } @@ -40,12 +50,13 @@ TxListItem.prototype.componentDidMount = async function () { const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data) const { name: txDataName } = decodedData || {} + const isTokenTx = txDataName === 'transfer' - const { total, fiatTotal } = txDataName === 'transfer' + const { total, fiatTotal } = isTokenTx ? await this.getSendTokenTotal() : this.getSendEtherTotal() - this.setState({ total, fiatTotal }) + this.setState({ total, fiatTotal, isTokenTx }) } TxListItem.prototype.getAddressText = function () { @@ -168,22 +179,49 @@ TxListItem.prototype.getSendTokenTotal = async function () { } } +TxListItem.prototype.showRetryButton = function () { + const { + transactionSubmittedTime, + selectedAddressTxList, + transactionId, + txParams, + } = this.props + const currentNonce = txParams.nonce + const currentNonceTxs = selectedAddressTxList.filter(tx => tx.txParams.nonce === currentNonce) + const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted') + const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[currentNonceSubmittedTxs.length - 1] + const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce + && lastSubmittedTxWithCurrentNonce.id === transactionId + + return currentTxIsLatestWithNonce && Date.now() - transactionSubmittedTime > 30000 +} + +TxListItem.prototype.setSelectedToken = function (tokenAddress) { + this.props.setSelectedToken(tokenAddress) +} + +TxListItem.prototype.resubmit = function () { + const { transactionId } = this.props + this.props.retryTransaction(transactionId) +} + TxListItem.prototype.render = function () { const { transactionStatus, transactionAmount, onClick, - transActionId, + transactionId, dateString, address, className, + txParams, } = this.props - const { total, fiatTotal } = this.state + const { total, fiatTotal, isTokenTx } = this.state const showFiatTotal = transactionAmount !== '0x0' && fiatTotal return h(`div${className || ''}`, { - key: transActionId, - onClick: () => onClick && onClick(transActionId), + key: transactionId, + onClick: () => onClick && onClick(transactionId), }, [ h(`div.flex-column.tx-list-item-wrapper`, {}, [ @@ -224,9 +262,10 @@ TxListItem.prototype.render = function () { className: classnames('tx-list-status', { 'tx-list-status--rejected': transactionStatus === 'rejected', 'tx-list-status--failed': transactionStatus === 'failed', + 'tx-list-status--dropped': transactionStatus === 'dropped', }), }, - transactionStatus, + this.txStatusIndicator(), ), ]), ]), @@ -241,6 +280,48 @@ TxListItem.prototype.render = function () { ]), ]), + + this.showRetryButton() && h('div.tx-list-item-retry-container', [ + + h('span.tx-list-item-retry-copy', 'Taking too long?'), + + h('span.tx-list-item-retry-link', { + onClick: (event) => { + event.stopPropagation() + if (isTokenTx) { + this.setSelectedToken(txParams.to) + } + this.resubmit() + }, + }, 'Increase the gas price on your transaction'), + + ]), + ]), // holding on icon from design ]) } + +TxListItem.prototype.txStatusIndicator = function () { + const { transactionStatus } = this.props + + let name + + if (transactionStatus === 'unapproved') { + name = t('unapproved') + } else if (transactionStatus === 'rejected') { + name = t('rejected') + } else if (transactionStatus === 'approved') { + name = t('approved') + } else if (transactionStatus === 'signed') { + name = t('signed') + } else if (transactionStatus === 'submitted') { + name = t('submitted') + } else if (transactionStatus === 'confirmed') { + name = t('confirmed') + } else if (transactionStatus === 'failed') { + name = t('failed') + } else if (transactionStatus === 'dropped') { + name = t('dropped') + } + return name +} diff --git a/ui/app/components/tx-list.js b/ui/app/components/tx-list.js index 34dc837ae..037c7de8c 100644 --- a/ui/app/components/tx-list.js +++ b/ui/app/components/tx-list.js @@ -40,7 +40,7 @@ TxList.prototype.render = function () { return h('div.flex-column', [ h('div.flex-row.tx-list-header-wrapper', [ h('div.flex-row.tx-list-header', [ - h('div', 'transactions'), + h('div', t('transactions')), ]), ]), h('div.flex-column.tx-list-container', {}, [ @@ -75,9 +75,10 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa address: transaction.txParams.to, transactionStatus: transaction.status, transactionAmount: transaction.txParams.value, - transActionId: transaction.id, + transactionId: transaction.id, transactionHash: transaction.hash, transactionNetworkId: transaction.metamaskNetworkId, + transactionSubmittedTime: transaction.submittedTime, } const { @@ -85,29 +86,31 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa transactionStatus, transactionAmount, dateString, - transActionId, + transactionId, transactionHash, transactionNetworkId, + transactionSubmittedTime, } = props const { showConfTxPage } = this.props const opts = { - key: transActionId || transactionHash, + key: transactionId || transactionHash, txParams: transaction.txParams, transactionStatus, - transActionId, + transactionId, dateString, address, transactionAmount, transactionHash, conversionRate, tokenInfoGetter: this.tokenInfoGetter, + transactionSubmittedTime, } const isUnapproved = transactionStatus === 'unapproved' if (isUnapproved) { - opts.onClick = () => showConfTxPage({id: transActionId}) + opts.onClick = () => showConfTxPage({id: transactionId}) opts.transactionStatus = t('Not Started') } else if (transactionHash) { opts.onClick = () => this.view(transactionHash, transactionNetworkId) diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js index 96d776270..bf2065106 100644 --- a/ui/app/components/tx-view.js +++ b/ui/app/components/tx-view.js @@ -69,13 +69,13 @@ TxView.prototype.renderButtons = function () { return !selectedToken ? ( h('div.flex-row.flex-center.hero-balance-buttons', [ - h('button.btn-clear.hero-balance-button.allcaps', { + h('button.btn-primary.hero-balance-button', { onClick: () => showModal({ name: 'DEPOSIT_ETHER', }), }, t('deposit')), - h('button.btn-clear.hero-balance-button.allcaps', { + h('button.btn-primary.hero-balance-button', { style: { marginLeft: '0.8em', }, @@ -85,7 +85,7 @@ TxView.prototype.renderButtons = function () { ) : ( h('div.flex-row.flex-center.hero-balance-buttons', [ - h('button.btn-clear.hero-balance-button', { + h('button.btn-primary.hero-balance-button', { onClick: showSendTokenPage, }, t('send')), ]) diff --git a/ui/app/components/typed-message-renderer.js b/ui/app/components/typed-message-renderer.js deleted file mode 100644 index d170d63b7..000000000 --- a/ui/app/components/typed-message-renderer.js +++ /dev/null @@ -1,42 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const extend = require('xtend') - -module.exports = TypedMessageRenderer - -inherits(TypedMessageRenderer, Component) -function TypedMessageRenderer () { - Component.call(this) -} - -TypedMessageRenderer.prototype.render = function () { - const props = this.props - const { value, style } = props - const text = renderTypedData(value) - - const defaultStyle = extend({ - width: '315px', - maxHeight: '210px', - resize: 'none', - border: 'none', - background: 'white', - padding: '3px', - overflow: 'scroll', - }, style) - - return ( - h('div.font-small', { - style: defaultStyle, - }, text) - ) -} - -function renderTypedData (values) { - return values.map(function (value) { - return h('div', {}, [ - h('strong', {style: {display: 'block', fontWeight: 'bold'}}, String(value.name) + ':'), - h('div', {}, value.value), - ]) - }) -} diff --git a/ui/app/components/wallet-content-display.js b/ui/app/components/wallet-content-display.js deleted file mode 100644 index bfa061be4..000000000 --- a/ui/app/components/wallet-content-display.js +++ /dev/null @@ -1,56 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits - -module.exports = WalletContentDisplay - -inherits(WalletContentDisplay, Component) -function WalletContentDisplay () { - Component.call(this) -} - -WalletContentDisplay.prototype.render = function () { - const { title, amount, fiatValue, active, style } = this.props - - // TODO: Separate component: wallet-content-account - return h('div.flex-column', { - style: { - marginLeft: '1.3em', - alignItems: 'flex-start', - ...style, - }, - }, [ - - h('span', { - style: { - fontSize: '1.1em', - }, - }, title), - - h('span', { - style: { - fontSize: '1.8em', - margin: '0.4em 0em', - }, - }, amount), - - h('span', { - style: { - fontSize: '1.3em', - }, - }, fiatValue), - - active && h('div', { - style: { - position: 'absolute', - marginLeft: '-1.3em', - height: '6em', - width: '0.3em', - background: '#D8D8D8', // $alto - }, - }, [ - ]), - ]) - -} - diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js index 18452205c..2c6d7f784 100644 --- a/ui/app/components/wallet-view.js +++ b/ui/app/components/wallet-view.js @@ -168,7 +168,7 @@ WalletView.prototype.render = function () { h(TokenList), - h('button.btn-clear.wallet-view__add-token-button', { + h('button.btn-primary.wallet-view__add-token-button', { onClick: () => { showAddTokenPage() hideSidebar() diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index b4ffc48b7..1070436c3 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -40,6 +40,7 @@ function mapStateToProps (state) { currentCurrency: state.metamask.currentCurrency, blockGasLimit: state.metamask.currentBlockGasLimit, computedBalances: state.metamask.computedBalances, + selectedAddressTxList: state.metamask.selectedAddressTxList, } } @@ -48,6 +49,23 @@ function ConfirmTxScreen () { Component.call(this) } +ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) { + const { + unapprovedTxs, + network, + selectedAddressTxList, + } = this.props + const { index: prevIndex, unapprovedTxs: prevUnapprovedTxs } = prevProps + const prevUnconfTxList = txHelper(prevUnapprovedTxs, {}, {}, {}, network) + const prevTxData = prevUnconfTxList[prevIndex] || {} + const prevTx = selectedAddressTxList.find(({ id }) => id === prevTxData.id) || {} + const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network) + + if (prevTx.status === 'dropped' && unconfTxList.length === 0) { + this.goHome({}) + } +} + ConfirmTxScreen.prototype.render = function () { const props = this.props const { diff --git a/ui/app/conversion-util.js b/ui/app/conversion-util.js index ee42ebea1..d484ed16d 100644 --- a/ui/app/conversion-util.js +++ b/ui/app/conversion-util.js @@ -187,6 +187,18 @@ const conversionGreaterThan = ( return firstValue.gt(secondValue) } +const conversionMax = ( + { ...firstProps }, + { ...secondProps }, +) => { + const firstIsGreater = conversionGreaterThan( + { ...firstProps }, + { ...secondProps } + ) + + return firstIsGreater ? firstProps.value : secondProps.value +} + const conversionGTE = ( { ...firstProps }, { ...secondProps }, @@ -216,6 +228,7 @@ module.exports = { conversionGreaterThan, conversionGTE, conversionLTE, + conversionMax, toNegative, subtractCurrencies, } diff --git a/ui/app/css/itcss/components/account-menu.scss b/ui/app/css/itcss/components/account-menu.scss index 4752741aa..c4037d862 100644 --- a/ui/app/css/itcss/components/account-menu.scss +++ b/ui/app/css/itcss/components/account-menu.scss @@ -87,7 +87,6 @@ flex: 1 0 auto; display: flex; flex-flow: column nowrap; - padding-top: 4px; } &__check-mark { @@ -115,13 +114,11 @@ color: $white; font-size: 18px; font-weight: 300; - line-height: 16px; } &__balance { color: $dusty-gray; font-size: 14px; - line-height: 19px; } &__action { diff --git a/ui/app/css/itcss/components/add-token.scss b/ui/app/css/itcss/components/add-token.scss index 13020f62f..bdf9da385 100644 --- a/ui/app/css/itcss/components/add-token.scss +++ b/ui/app/css/itcss/components/add-token.scss @@ -172,11 +172,8 @@ justify-content: center; } - &__button { - flex: 1 0 141px; - margin: 0 12px; - padding: 10px 22px; - height: 54px; + &__cancel-button { + margin-right: 1.2rem; } &__token-icons-container { @@ -334,7 +331,7 @@ } &__buttons { - padding: 12px 0; + padding: 1rem; margin: 0; border-top: 1px solid $gallery; width: 100%; diff --git a/ui/app/css/itcss/components/buttons.scss b/ui/app/css/itcss/components/buttons.scss index 8df8829f2..04e1ed96e 100644 --- a/ui/app/css/itcss/components/buttons.scss +++ b/ui/app/css/itcss/components/buttons.scss @@ -2,6 +2,76 @@ Buttons */ +.btn-primary, +.btn-primary--lg, +.btn-secondary, +.btn-secondary--lg { + background: $white; + display: flex; + justify-content: center; + align-items: center; + box-sizing: border-box; + border-radius: 4px; + font-size: 14px; + font-weight: 500; + transition: border-color .3s ease; + padding: 0 20px; + min-width: 140px; + text-transform: uppercase; +} + +.btn-primary, +.btn-primary--lg { + color: $curious-blue; + border: 2px solid $spindle; + + &:active { + background: $zumthor; + border-color: $curious-blue; + } + + &:hover { + border-color: $curious-blue; + } + + &--disabled, + &[disabled] { + cursor: auto; + opacity: .5; + pointer-events: none; + } +} + +.btn-secondary, +.btn-secondary--lg { + color: $scorpion; + border: 2px solid $dusty-gray; + + &:active { + background: $gallery; + border-color: $dusty-gray; + } + + &:hover { + border-color: $scorpion; + } + + &--disabled, + &[disabled] { + cursor: auto; + opacity: .5; + pointer-events: none; + } +} + +.btn-primary, .btn-secondary { + height: 44px; +} + +.btn-primary--lg, .btn-secondary--lg { + height: 54px; +} + .btn-green { background-color: #02c9b1; // TODO: reusable color in colors.css } @@ -130,20 +200,6 @@ button.btn-thin { font-size: 13px; } -.btn-secondary { - border: 1px solid #979797; - border-radius: 2px; - background-color: $white; - font-size: 16px; - line-height: 24px; - padding: 16px 42px; - - &[disabled] { - background-color: $white !important; - opacity: .5; - } -} - .btn-tertiary { border: 1px solid transparent; border-radius: 2px; diff --git a/ui/app/css/itcss/components/confirm.scss b/ui/app/css/itcss/components/confirm.scss index 1977b49ae..abe138f54 100644 --- a/ui/app/css/itcss/components/confirm.scss +++ b/ui/app/css/itcss/components/confirm.scss @@ -242,6 +242,22 @@ section .confirm-screen-account-number, } } +@media screen and (max-width: 379px) { + .confirm-screen-row { + span.confirm-screen-section-column { + flex: 0.4; + } + + div.confirm-screen-section-column { + flex: 0.6; + } + + .currency-display__input { + font-size: 14px; + } + } +} + .confirm-screen-row-detail { font-size: 12px; line-height: 16px; diff --git a/ui/app/css/itcss/components/hero-balance.scss b/ui/app/css/itcss/components/hero-balance.scss index a3f051361..69cde8a0f 100644 --- a/ui/app/css/itcss/components/hero-balance.scss +++ b/ui/app/css/itcss/components/hero-balance.scss @@ -103,10 +103,11 @@ } .hero-balance-button { + min-width: initial; width: 6rem; @media #{$sub-mid-size-breakpoint-range} { - padding: 0.4rem; + padding: .4rem; width: 4rem; display: flex; flex: 1; diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss index a8d5e8dc2..9ae3ea7fa 100644 --- a/ui/app/css/itcss/components/modal.scss +++ b/ui/app/css/itcss/components/modal.scss @@ -261,7 +261,7 @@ .account-modal__button { margin-top: 17px; padding: 10px 22px; - width: 235px; + width: 286px; } } @@ -341,9 +341,8 @@ .export-private-key__button { margin-top: 17px; - padding: 10px 22px; width: 141px; - height: 54px; + min-width: initial; } .export-private-key__button--cancel { @@ -765,15 +764,7 @@ } &__deposit-button, .shapeshift-form__shapeshift-buy-btn { - height: 54px; width: 257px; - border: 1px solid $curious-blue; - border-radius: 4px; - display: flex; - justify-content: center; - font-size: 16px; - color: $curious-blue; - background-color: $white; } .shapeshift-form-wrapper { diff --git a/ui/app/css/itcss/components/network.scss b/ui/app/css/itcss/components/network.scss index c32d1de5e..c34b5cd06 100644 --- a/ui/app/css/itcss/components/network.scss +++ b/ui/app/css/itcss/components/network.scss @@ -10,8 +10,9 @@ .network-component.pointer { border: 2px solid $silver; border-radius: 82px; - padding: 3px; + padding: 7px 3px; flex: 0 0 auto; + display: flex; &.ethereum-network .menu-icon-circle div { background-color: rgba(3, 135, 137, .7) !important; @@ -158,3 +159,15 @@ .network-caret { margin: 0 8px 2px; } + +.network-display { + &__container { + display: flex; + align-items: center; + justify-content: flex-start; + + @media screen and (min-width: 576px) { + display: none; + } + } +} diff --git a/ui/app/css/itcss/components/new-account.scss b/ui/app/css/itcss/components/new-account.scss index c2cefe4ad..aa7fed956 100644 --- a/ui/app/css/itcss/components/new-account.scss +++ b/ui/app/css/itcss/components/new-account.scss @@ -192,29 +192,8 @@ justify-content: space-between; } - &__button-cancel, - &__button-create { - height: 55px; + &__button { width: 150px; - border-radius: 2px; - background-color: #FFFFFF; - } - - &__button-cancel { - border: 1px solid $dusty-gray; - color: $dusty-gray; - font-family: Roboto; - font-size: 16px; - line-height: 21px; - text-align: center; + min-width: initial; } - - &__button-create { - border: 1px solid $curious-blue; - color: $curious-blue; - font-family: Roboto; - font-size: 16px; - line-height: 21px; - text-align: center; - } -}
\ No newline at end of file +} diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss index 5cdda5e6c..777a82318 100644 --- a/ui/app/css/itcss/components/newui-sections.scss +++ b/ui/app/css/itcss/components/newui-sections.scss @@ -265,7 +265,6 @@ $wallet-view-bg: $alabaster; .account-name { font-size: 24px; font-weight: 300; - line-height: 20px; color: $black; margin-top: 8px; margin-bottom: .9rem; diff --git a/ui/app/css/itcss/components/request-signature.scss b/ui/app/css/itcss/components/request-signature.scss index d81099bfa..a4728afef 100644 --- a/ui/app/css/itcss/components/request-signature.scss +++ b/ui/app/css/itcss/components/request-signature.scss @@ -190,41 +190,19 @@ width: 100%; display: flex; align-items: center; - justify-content: space-evenly; + justify-content: center; font-size: 22px; position: relative; flex: 0 0 auto; border-top: 1px solid $geyser; + padding: 1.6rem; - &__cancel-button, - &__sign-button { - display: flex; - align-items: center; - justify-content: center; - flex: 1 0 auto; - font-family: Roboto; - font-size: 16px; - font-weight: 300; - height: 55px; - line-height: 32px; - cursor: pointer; - border-radius: 2px; - box-shadow: none; - max-width: 162px; - margin: 12px; + button { + width: 165px; } &__cancel-button { - background: none; - border: 1px solid $dusty-gray; - margin-right: 6px; - } - - &__sign-button { - background-color: $caribbean-green; - border-width: 0; - color: $white; - margin-left: 6px; + margin-right: 1.2rem; } } -}
\ No newline at end of file +} diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss index bb17e53cd..89d2be891 100644 --- a/ui/app/css/itcss/components/send.scss +++ b/ui/app/css/itcss/components/send.scss @@ -526,14 +526,10 @@ } &__form { - padding: 13px 0; - width: 100%; - overflow-y: auto; + padding: 10px 0 25px; @media screen and (max-width: $break-small) { - padding: 13px 0; margin: 0; - overflow-y: auto; flex: 1 1 auto; } } @@ -660,6 +656,13 @@ &__gas-fee-display { width: 100%; + position: relative; + + .currency-display--message { + padding: 8px 38px 8px 10px; + display: flex; + align-items: center; + } } &__sliders-icon-container { @@ -779,7 +782,6 @@ &__buttons { display: flex; justify-content: space-between; - width: 181.75px; margin-right: 21.25px; } @@ -797,13 +799,8 @@ } &__cancel, &__save, &__save__error { - height: 34.64px; width: 85.74px; - border: 1px solid $dusty-gray; - border-radius: 2px; - font-family: 'DIN OT'; - font-size: 12px; - color: $dusty-gray; + min-width: initial; } &__save__error { @@ -885,3 +882,23 @@ } } } + +.sliders-icon-container { + display: flex; + align-items: center; + justify-content: center; + height: 24px; + width: 24px; + border: 1px solid $curious-blue; + border-radius: 4px; + background-color: $white; + position: absolute; + right: 15px; + top: 14px; + cursor: pointer; + font-size: 1em; +} + +.sliders-icon { + color: $curious-blue; +}
\ No newline at end of file diff --git a/ui/app/css/itcss/components/settings.scss b/ui/app/css/itcss/components/settings.scss index d60ebd934..dcc9b98d5 100644 --- a/ui/app/css/itcss/components/settings.scss +++ b/ui/app/css/itcss/components/settings.scss @@ -130,24 +130,32 @@ cursor: pointer; } -.settings__clear-button { - font-size: 16px; - border: 1px solid $curious-blue; - color: $curious-blue; - border-radius: 2px; - padding: 18px; - background-color: $white; - text-transform: uppercase; -} - -.settings__clear-button--red { - border: 1px solid $monzo; +.settings__button--red { + border-color: lighten($monzo, 10%); color: $monzo; + + &:active { + background: lighten($monzo, 55%); + border-color: $monzo; + } + + &:hover { + border-color: $monzo; + } } -.settings__clear-button--orange { - border: 1px solid rgba(247, 134, 28, 1); - color: rgba(247, 134, 28, 1); +.settings__button--orange { + border-color: lighten($ecstasy, 20%); + color: $ecstasy; + + &:active { + background: lighten($ecstasy, 40%); + border-color: $ecstasy; + } + + &:hover { + border-color: $ecstasy; + } } .settings__info-logo-wrapper { diff --git a/ui/app/css/itcss/components/transaction-list.scss b/ui/app/css/itcss/components/transaction-list.scss index c3df493df..d03faf486 100644 --- a/ui/app/css/itcss/components/transaction-list.scss +++ b/ui/app/css/itcss/components/transaction-list.scss @@ -97,7 +97,7 @@ .tx-list-content-wrapper { align-items: stretch; - margin-bottom: 4px; + margin: 4px 0; flex: 1 0 auto; width: 100%; display: flex; @@ -126,6 +126,54 @@ } } +.tx-list-item-retry-container { + background: #d1edff; + width: 100%; + border-radius: 4px; + font-size: 0.8em; + display: flex; + justify-content: center; + margin-left: 44px; + width: calc(100% - 44px); + + @media screen and (min-width: 576px) and (max-width: 679px) { + flex-flow: column; + align-items: center; + } + + @media screen and (min-width: 380px) and (max-width: 575px) { + flex-flow: row; + } + + @media screen and (max-width: 379px) { + flex-flow: column; + align-items: center; + } +} + +.tx-list-item-retry-copy { + font-family: Roboto; +} + +.tx-list-item-retry-link { + text-decoration: underline; + margin-left: 6px; + cursor: pointer; + + @media screen and (min-width: 576px) and (max-width: 679px) { + margin-left: 0px; + } + + @media screen and (min-width: 380px) and (max-width: 575px) { + margin-left: 6px; + } + + @media screen and (max-width: 379px) { + margin-left: 0px; + text-align: center; + } +} + .tx-list-date { color: $dusty-gray; font-size: 12px; @@ -136,6 +184,7 @@ align-self: center; flex: 0 0 auto; margin-right: 16px; + display: flex; } .tx-list-account-and-status-wrapper { @@ -189,6 +238,10 @@ .tx-list-status--failed { color: $monzo; } + + .tx-list-status--dropped { + opacity: 0.5; + } } .tx-list-item { diff --git a/ui/app/css/itcss/generic/index.scss b/ui/app/css/itcss/generic/index.scss index 1fbd9896f..92321394b 100644 --- a/ui/app/css/itcss/generic/index.scss +++ b/ui/app/css/itcss/generic/index.scss @@ -13,7 +13,6 @@ body { font-family: Roboto, Arial; color: #4d4d4d; font-weight: 300; - line-height: 1.4em; background: #f7f7f7; width: 100%; height: 100%; @@ -103,9 +102,16 @@ input.large-input { &::after { content: '\00D7'; font-size: 40px; + line-height: 20px; } } + &__header-row { + padding-bottom: 10px; + display: flex; + justify-content: space-between; + } + &__footer { display: flex; flex-flow: row; @@ -126,7 +132,7 @@ input.large-input { height: 55px; font-size: 1rem; text-transform: uppercase; - margin-right: 1rem; + margin-right: 1.2rem; border-radius: 2px; &:last-of-type { @@ -138,7 +144,6 @@ input.large-input { color: #2f9ae0; font-size: 1rem; cursor: pointer; - padding-bottom: 10px; font-weight: 400; } diff --git a/ui/app/css/itcss/generic/reset.scss b/ui/app/css/itcss/generic/reset.scss index e054d533e..a417a0453 100644 --- a/ui/app/css/itcss/generic/reset.scss +++ b/ui/app/css/itcss/generic/reset.scss @@ -112,10 +112,6 @@ section { display: block; } -body { - line-height: 1; -} - ol, ul { list-style: none; diff --git a/ui/app/css/itcss/settings/variables.scss b/ui/app/css/itcss/settings/variables.scss index d96c1ae43..0031238a8 100644 --- a/ui/app/css/itcss/settings/variables.scss +++ b/ui/app/css/itcss/settings/variables.scss @@ -46,10 +46,13 @@ $manatee: #93949d; $spindle: #c7ddec; $mid-gray: #5b5d67; $cape-cod: #38393a; +$onahau: #d1edff; $java: #29b6af; $wild-strawberry: #ff4a8d; $cornflower-blue: #7057ff; $saffron: #f6c343; +$zumthor: #edf7ff; +$ecstasy: #f7861c; /* Z-Indicies diff --git a/ui/app/info.js b/ui/app/info.js deleted file mode 100644 index 49ff9f24a..000000000 --- a/ui/app/info.js +++ /dev/null @@ -1,154 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') -const connect = require('react-redux').connect -const actions = require('./actions') - -module.exports = connect(mapStateToProps)(InfoScreen) - -function mapStateToProps (state) { - return {} -} - -inherits(InfoScreen, Component) -function InfoScreen () { - Component.call(this) -} - -InfoScreen.prototype.render = function () { - const state = this.props - const version = global.platform.getVersion() - - return ( - h('.flex-column.flex-grow', { - style: { - maxWidth: '400px', - }, - }, [ - - // subtitle and nav - h('.section-title.flex-row.flex-center', [ - h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { - onClick: (event) => { - state.dispatch(actions.goHome()) - }, - }), - h('h2.page-subtitle', 'Info'), - ]), - - // main view - h('.flex-column.flex-justify-center.flex-grow.select-none', [ - h('.flex-space-around', { - style: { - padding: '20px', - }, - }, [ - // current version number - - h('.info.info-gray', [ - h('div', 'Metamask'), - h('div', { - style: { - marginBottom: '10px', - }, - }, `Version: ${version}`), - ]), - - h('div', { - style: { - marginBottom: '5px', - }}, - [ - h('div', [ - h('a', { - href: 'https://metamask.io/privacy.html', - target: '_blank', - onClick (event) { this.navigateTo(event.target.href) }, - }, [ - h('div.info', 'Privacy Policy'), - ]), - ]), - h('div', [ - h('a', { - href: 'https://metamask.io/terms.html', - target: '_blank', - onClick (event) { this.navigateTo(event.target.href) }, - }, [ - h('div.info', 'Terms of Use'), - ]), - ]), - h('div', [ - h('a', { - href: 'https://metamask.io/attributions.html', - target: '_blank', - onClick (event) { this.navigateTo(event.target.href) }, - }, [ - h('div.info', 'Attributions'), - ]), - ]), - ] - ), - - h('hr', { - style: { - margin: '10px 0 ', - width: '7em', - }, - }), - - h('div', { - style: { - paddingLeft: '30px', - }}, - [ - h('div.fa.fa-support', [ - h('a.info', { - href: 'https://metamask.helpscoutdocs.com/', - target: '_blank', - }, 'Visit our Knowledge Base'), - ]), - - h('div', [ - h('a', { - href: 'https://metamask.io/', - target: '_blank', - }, [ - h('img.icon-size', { - src: 'images/icon-128.png', - style: { - // IE6-9 - filter: 'grayscale(100%)', - // Microsoft Edge and Firefox 35+ - WebkitFilter: 'grayscale(100%)', - }, - }), - h('div.info', 'Visit our web site'), - ]), - ]), - - h('div', [ - h('.fa.fa-twitter', [ - h('a.info', { - href: 'https://twitter.com/metamask_io', - target: '_blank', - }, 'Follow us on Twitter'), - ]), - ]), - - h('div.fa.fa-envelope', [ - h('a.info', { - target: '_blank', - href: 'mailto:support@metamask.io?subject=MetaMask Support', - }, 'Email us!'), - ]), - ]), - ]), - ]), - ]) - ) -} - -InfoScreen.prototype.navigateTo = function (url) { - global.platform.openWindow({ url }) -} - diff --git a/ui/app/keychains/hd/recover-seed/confirmation.js b/ui/app/keychains/hd/recover-seed/confirmation.js index 4335186a5..bc5339549 100644 --- a/ui/app/keychains/hd/recover-seed/confirmation.js +++ b/ui/app/keychains/hd/recover-seed/confirmation.js @@ -4,6 +4,7 @@ const Component = require('react').Component const connect = require('react-redux').connect const h = require('react-hyperscript') const actions = require('../../../actions') +const t = require('../../../../i18n') module.exports = connect(mapStateToProps)(RevealSeedConfirmation) @@ -49,13 +50,13 @@ RevealSeedConfirmation.prototype.render = function () { }, }, [ - h('h4', 'Do not recover your seed words in a public place! These words can be used to steal all your accounts.'), + h('h4', t('revealSeedWordsWarning')), // confirmation h('input.large-input.letter-spacey', { type: 'password', id: 'password-box', - placeholder: 'Enter your password to confirm', + placeholder: t('enterPasswordConfirm'), onKeyPress: this.checkConfirmation.bind(this), style: { width: 260, @@ -91,7 +92,7 @@ RevealSeedConfirmation.prototype.render = function () { ), props.inProgress && ( - h('span.in-progress-notification', 'Generating Seed...') + h('span.in-progress-notification', t('generatingSeed')) ), ]), ]) diff --git a/ui/app/keychains/hd/restore-vault.js b/ui/app/keychains/hd/restore-vault.js index cb4088f61..5e4e004cf 100644 --- a/ui/app/keychains/hd/restore-vault.js +++ b/ui/app/keychains/hd/restore-vault.js @@ -2,6 +2,7 @@ const inherits = require('util').inherits const PersistentForm = require('../../../lib/persistent-form') const connect = require('react-redux').connect const h = require('react-hyperscript') +const t = require('../../../i18n') const actions = require('../../actions') module.exports = connect(mapStateToProps)(RestoreVaultScreen) @@ -36,23 +37,23 @@ RestoreVaultScreen.prototype.render = function () { padding: 6, }, }, [ - 'Restore Vault', + t('restoreVault'), ]), // wallet seed entry - h('h3', 'Wallet Seed'), + h('h3', t('walletSeed')), h('textarea.twelve-word-phrase.letter-spacey', { dataset: { persistentFormId: 'wallet-seed', }, - placeholder: 'Enter your secret twelve word phrase here to restore your vault.', + placeholder: t('secretPhrase'), }), // password h('input.large-input.letter-spacey', { type: 'password', id: 'password-box', - placeholder: 'New Password (min 8 chars)', + placeholder: t('newPassword8Chars'), dataset: { persistentFormId: 'password', }, @@ -66,7 +67,7 @@ RestoreVaultScreen.prototype.render = function () { h('input.large-input.letter-spacey', { type: 'password', id: 'password-box-confirm', - placeholder: 'Confirm Password', + placeholder: t('confirmPassword'), onKeyPress: this.createOnEnter.bind(this), dataset: { persistentFormId: 'password-confirmation', @@ -93,16 +94,20 @@ RestoreVaultScreen.prototype.render = function () { // cancel h('button.primary', { onClick: this.showInitializeMenu.bind(this), - }, 'CANCEL'), + style: { + textTransform: 'uppercase', + }, + }, t('cancel')), // submit h('button.primary', { onClick: this.createNewVaultAndRestore.bind(this), - }, 'OK'), - + style: { + textTransform: 'uppercase', + }, + }, t('ok')), ]), ]) - ) } @@ -131,13 +136,13 @@ RestoreVaultScreen.prototype.createNewVaultAndRestore = function () { var passwordConfirmBox = document.getElementById('password-box-confirm') var passwordConfirm = passwordConfirmBox.value if (password.length < 8) { - this.warning = 'Password not long enough' + this.warning = t('passwordNotLongEnough') this.props.dispatch(actions.displayWarning(this.warning)) return } if (password !== passwordConfirm) { - this.warning = 'Passwords don\'t match' + this.warning = t('passwordsDontMatch') this.props.dispatch(actions.displayWarning(this.warning)) return } @@ -147,18 +152,18 @@ RestoreVaultScreen.prototype.createNewVaultAndRestore = function () { // true if the string has more than a space between words. if (seed.split(' ').length > 1) { - this.warning = 'there can only be a space between words' + this.warning = t('spaceBetween') this.props.dispatch(actions.displayWarning(this.warning)) return } // true if seed contains a character that is not between a-z or a space if (!seed.match(/^[a-z ]+$/)) { - this.warning = 'seed words only have lowercase characters' - this.props.dispatch(actions.displayWarning(this.warning)) + this.warning = t('loweCaseWords') + this.props.dispatch(actions.displayWarning(this.warning)) return } if (seed.split(' ').length !== 12) { - this.warning = 'seed phrases are 12 words long' + this.warning = t('seedPhraseReq') this.props.dispatch(actions.displayWarning(this.warning)) return } diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index 4ca7d221e..9cba5e83b 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -38,6 +38,8 @@ function reduceMetamask (state, action) { errors: {}, maxModeOn: false, editingTransactionId: null, + forceGasMin: null, + toNickname: '', }, coinOptions: {}, useBlockie: false, @@ -237,7 +239,8 @@ function reduceMetamask (state, action) { return extend(metamaskState, { send: { ...metamaskState.send, - to: action.value, + to: action.value.to, + toNickname: action.value.nickname, }, }) @@ -297,6 +300,7 @@ function reduceMetamask (state, action) { memo: '', errors: {}, editingTransactionId: null, + forceGasMin: null, }, }) diff --git a/ui/app/selectors.js b/ui/app/selectors.js index 5d2635775..2bdc39004 100644 --- a/ui/app/selectors.js +++ b/ui/app/selectors.js @@ -18,6 +18,7 @@ const selectors = { getCurrentAccountWithSendEtherInfo, getGasPrice, getGasLimit, + getForceGasMin, getAddressBook, getSendFrom, getCurrentCurrency, @@ -55,8 +56,9 @@ function getSelectedToken (state) { const tokens = state.metamask.tokens || [] const selectedTokenAddress = state.metamask.selectedTokenAddress const selectedToken = tokens.filter(({ address }) => address === selectedTokenAddress)[0] + const sendToken = state.metamask.send.token - return selectedToken || null + return selectedToken || sendToken || null } function getSelectedTokenExchangeRate (state) { @@ -130,6 +132,10 @@ function getGasLimit (state) { return state.metamask.send.gasLimit } +function getForceGasMin (state) { + return state.metamask.send.forceGasMin +} + function getSendFrom (state) { return state.metamask.send.from } diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index fc1df1f51..620da73f8 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -1,12 +1,13 @@ const { inherits } = require('util') const PersistentForm = require('../lib/persistent-form') const h = require('react-hyperscript') +const t = require('../i18n') const ethAbi = require('ethereumjs-abi') const ethUtil = require('ethereumjs-util') const FromDropdown = require('./components/send/from-dropdown') -const ToAutoComplete = require('./components/send/to-autocomplete') +const EnsInput = require('./components/ens-input') const CurrencyDisplay = require('./components/send/currency-display') const MemoTextArea = require('./components/send/memo-textarea') const GasFeeDisplay = require('./components/send/gas-fee-display-v2') @@ -42,6 +43,7 @@ function SendTransactionScreen () { to: null, amount: null, }, + gasLoadingError: false, } this.handleToChange = this.handleToChange.bind(this) @@ -128,6 +130,10 @@ SendTransactionScreen.prototype.updateGas = function () { .then(([gasPrice, gas]) => { const newGasTotal = this.getGasTotal(gas, gasPrice) updateGasTotal(newGasTotal) + this.setState({ gasLoadingError: false }) + }) + .catch(err => { + this.setState({ gasLoadingError: true }) }) } else { const newGasTotal = this.getGasTotal(gasLimit, gasPrice) @@ -180,13 +186,12 @@ SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) { SendTransactionScreen.prototype.renderHeader = function () { const { selectedToken, clearSend, goHome } = this.props - const tokenText = selectedToken ? 'tokens' : 'ETH' return h('div.page-container__header', [ - h('div.page-container__title', selectedToken ? 'Send Tokens' : 'Send ETH'), + h('div.page-container__title', selectedToken ? t('sendTokens') : t('sendETH')), - h('div.page-container__subtitle', `Only send ${tokenText} to an Ethereum address.`), + h('div.page-container__subtitle', t('onlySendToEtherAddress')), h('div.page-container__header-close', { onClick: () => { @@ -248,7 +253,7 @@ SendTransactionScreen.prototype.renderFromRow = function () { ]) } -SendTransactionScreen.prototype.handleToChange = function (to) { +SendTransactionScreen.prototype.handleToChange = function (to, nickname = '') { const { updateSendTo, updateSendErrors, @@ -257,19 +262,19 @@ SendTransactionScreen.prototype.handleToChange = function (to) { let toError = null if (!to) { - toError = 'Required' + toError = t('required') } else if (!isValidAddress(to)) { - toError = 'Recipient address is invalid' + toError = t('invalidAddressRecipient') } else if (to === from) { - toError = 'From and To address cannot be the same' + toError = t('fromToSame') } - updateSendTo(to) + updateSendTo(to, nickname) updateSendErrors({ to: toError }) } SendTransactionScreen.prototype.renderToRow = function () { - const { toAccounts, errors, to } = this.props + const { toAccounts, errors, to, network } = this.props const { toDropdownOpen } = this.state @@ -277,14 +282,17 @@ SendTransactionScreen.prototype.renderToRow = function () { h('div.send-v2__form-label', [ - 'To:', + t('to'), - this.renderErrorMessage('to'), + this.renderErrorMessage(t('to')), ]), h('div.send-v2__form-field', [ - h(ToAutoComplete, { + h(EnsInput, { + name: 'address', + placeholder: 'Recipient Address', + network, to, accounts: Object.entries(toAccounts).map(([key, account]) => account), dropdownOpen: toDropdownOpen, @@ -377,11 +385,11 @@ SendTransactionScreen.prototype.validateAmount = function (value) { ) if (conversionRate && !sufficientBalance) { - amountError = 'Insufficient funds.' + amountError = t('insufficientFunds') } else if (verifyTokenBalance && !sufficientTokens) { - amountError = 'Insufficient tokens.' + amountError = t('insufficientTokens') } else if (amountLessThanZero) { - amountError = 'Can not send negative amounts of ETH.' + amountError = t('negativeETH') } updateSendErrors({ amount: amountError }) @@ -411,7 +419,7 @@ SendTransactionScreen.prototype.renderAmountRow = function () { setMaxModeTo(true) this.setAmountToMax() }, - }, [ !maxModeOn ? 'Max' : '' ]), + }, [ !maxModeOn ? t('max') : '' ]), ]), h('div.send-v2__form-field', [ @@ -436,10 +444,11 @@ SendTransactionScreen.prototype.renderGasRow = function () { showCustomizeGasModal, gasTotal, } = this.props + const { gasLoadingError } = this.state return h('div.send-v2__form-row', [ - h('div.send-v2__form-label', 'Gas fee:'), + h('div.send-v2__form-label', h('gasFee')), h('div.send-v2__form-field', [ @@ -448,6 +457,7 @@ SendTransactionScreen.prototype.renderGasRow = function () { conversionRate, convertedCurrency, onClick: showCustomizeGasModal, + gasLoadingError, }), ]), @@ -473,18 +483,19 @@ SendTransactionScreen.prototype.renderMemoRow = function () { } SendTransactionScreen.prototype.renderForm = function () { - return h('div.send-v2__form', {}, [ - - this.renderFromRow(), + return h('.page-container__content', {}, [ + h('.send-v2__form', [ + this.renderFromRow(), - this.renderToRow(), + this.renderToRow(), - this.renderAmountRow(), + this.renderAmountRow(), - this.renderGasRow(), + this.renderGasRow(), - // this.renderMemoRow(), + // this.renderMemoRow(), + ]), ]) } @@ -502,16 +513,16 @@ SendTransactionScreen.prototype.renderFooter = function () { const noErrors = !amountError && toError === null return h('div.page-container__footer', [ - h('button.btn-cancel.page-container__footer-button', { + h('button.btn-secondary--lg.page-container__footer-button', { onClick: () => { clearSend() goHome() }, - }, 'Cancel'), - h('button.btn-clear.page-container__footer-button', { + }, t('cancel')), + h('button.btn-primary--lg.page-container__footer-button', { disabled: !noErrors || !gasTotal || missingTokenBalance, onClick: event => this.onSubmit(event), - }, 'Next'), + }, t('next')), ]) } @@ -530,11 +541,11 @@ SendTransactionScreen.prototype.render = function () { ) } -SendTransactionScreen.prototype.addToAddressBookIfNew = function (newAddress) { +SendTransactionScreen.prototype.addToAddressBookIfNew = function (newAddress, nickname = '') { const { toAccounts, addToAddressBook } = this.props if (!toAccounts.find(({ address }) => newAddress === address)) { // TODO: nickname, i.e. addToAddressBook(recipient, nickname) - addToAddressBook(newAddress) + addToAddressBook(newAddress, nickname) } } @@ -571,9 +582,11 @@ SendTransactionScreen.prototype.getEditedTx = function () { data, }) } else { + const data = unapprovedTxs[editingTransactionId].txParams.data Object.assign(editingTx.txParams, { value: ethUtil.addHexPrefix(amount), to: ethUtil.addHexPrefix(to), + data, }) } @@ -593,6 +606,7 @@ SendTransactionScreen.prototype.onSubmit = function (event) { updateTx, selectedToken, editingTransactionId, + toNickname, errors: { amount: amountError, to: toError }, } = this.props @@ -602,7 +616,7 @@ SendTransactionScreen.prototype.onSubmit = function (event) { return } - this.addToAddressBookIfNew(to) + this.addToAddressBookIfNew(to, toNickname) if (editingTransactionId) { const editedTx = this.getEditedTx() diff --git a/ui/app/send.js b/ui/app/send.js deleted file mode 100644 index 517b7690d..000000000 --- a/ui/app/send.js +++ /dev/null @@ -1,547 +0,0 @@ -// const { inherits } = require('util') -// const PersistentForm = require('../lib/persistent-form') -// const h = require('react-hyperscript') -// const connect = require('react-redux').connect -// const Identicon = require('./components/identicon') -// const EnsInput = require('./components/ens-input') -// const GasTooltip = require('./components/send/gas-tooltip') -// const CurrencyToggle = require('./components/send/currency-toggle') -// const GasFeeDisplay = require('./components/send/gas-fee-display') -// const { getSelectedIdentity } = require('./selectors') - -// const { -// showAccountsPage, -// backToAccountDetail, -// displayWarning, -// hideWarning, -// addToAddressBook, -// signTx, -// estimateGas, -// getGasPrice, -// } = require('./actions') -// const { stripHexPrefix, addHexPrefix } = require('ethereumjs-util') -// const { isHex, numericBalance, isValidAddress, allNull } = require('./util') -// const { conversionUtil, conversionGreaterThan } = require('./conversion-util') - -// module.exports = connect(mapStateToProps)(SendTransactionScreen) - -// function mapStateToProps (state) { -// const { -// selectedAddress: address, -// accounts, -// identities, -// network, -// addressBook, -// conversionRate, -// currentBlockGasLimit: blockGasLimit, -// } = state.metamask -// const { warning } = state.appState -// const selectedIdentity = getSelectedIdentity(state) -// const account = accounts[address] - -// return { -// address, -// accounts, -// identities, -// network, -// addressBook, -// conversionRate, -// blockGasLimit, -// warning, -// selectedIdentity, -// error: warning && warning.split('.')[0], -// account, -// identity: identities[address], -// balance: account ? account.balance : null, -// } -// } - -// inherits(SendTransactionScreen, PersistentForm) -// function SendTransactionScreen () { -// PersistentForm.call(this) - -// // [WIP] These are the bare minimum of tx props needed to sign a transaction -// // We will need a few more for contract-related interactions -// this.state = { -// newTx: { -// from: '', -// to: '', -// amountToSend: '0x0', -// gasPrice: null, -// gas: null, -// amount: '0x0', -// txData: null, -// memo: '', -// }, -// activeCurrency: 'USD', -// tooltipIsOpen: false, -// errors: {}, -// isValid: false, -// } - -// this.back = this.back.bind(this) -// this.closeTooltip = this.closeTooltip.bind(this) -// this.onSubmit = this.onSubmit.bind(this) -// this.setActiveCurrency = this.setActiveCurrency.bind(this) -// this.toggleTooltip = this.toggleTooltip.bind(this) -// this.validate = this.validate.bind(this) -// this.getAmountToSend = this.getAmountToSend.bind(this) -// this.setErrorsFor = this.setErrorsFor.bind(this) -// this.clearErrorsFor = this.clearErrorsFor.bind(this) - -// this.renderFromInput = this.renderFromInput.bind(this) -// this.renderToInput = this.renderToInput.bind(this) -// this.renderAmountInput = this.renderAmountInput.bind(this) -// this.renderGasInput = this.renderGasInput.bind(this) -// this.renderMemoInput = this.renderMemoInput.bind(this) -// this.renderErrorMessage = this.renderErrorMessage.bind(this) -// } - -// SendTransactionScreen.prototype.componentWillMount = function () { -// const { newTx } = this.state -// const { address } = this.props - -// Promise.all([ -// this.props.dispatch(getGasPrice()), -// this.props.dispatch(estimateGas({ -// from: address, -// gas: '746a528800', -// })), -// ]) -// .then(([blockGasPrice, estimatedGas]) => { -// console.log({ blockGasPrice, estimatedGas}) -// this.setState({ -// newTx: { -// ...newTx, -// gasPrice: blockGasPrice, -// gas: estimatedGas, -// }, -// }) -// }) -// } - -// SendTransactionScreen.prototype.renderErrorMessage = function(errorType, warning) { -// const { errors } = this.state -// const errorMessage = errors[errorType]; - -// return errorMessage || warning -// ? h('div.send-screen-input-wrapper__error-message', [ errorMessage || warning ]) -// : null -// } - -// SendTransactionScreen.prototype.renderFromInput = function (from, identities) { -// return h('div.send-screen-input-wrapper', [ - -// h('div', 'From:'), - -// h('input.large-input.send-screen-input', { -// list: 'accounts', -// placeholder: 'Account', -// value: from, -// onChange: (event) => { -// this.setState({ -// newTx: { -// ...this.state.newTx, -// from: event.target.value, -// }, -// }) -// }, -// onBlur: () => this.setErrorsFor('from'), -// onFocus: event => { -// this.clearErrorsFor('from') -// this.state.newTx.from && event.target.select() -// }, -// }), - -// h('datalist#accounts', [ -// Object.entries(identities).map(([key, { address, name }]) => { -// return h('option', { -// value: address, -// label: name, -// key: address, -// }) -// }), -// ]), - -// this.renderErrorMessage('from'), - -// ]) -// } - -// SendTransactionScreen.prototype.renderToInput = function (to, identities, addressBook) { -// return h('div.send-screen-input-wrapper', [ - -// h('div', 'To:'), - -// h('input.large-input.send-screen-input', { -// name: 'address', -// list: 'addresses', -// placeholder: 'Address', -// value: to, -// onChange: (event) => { -// this.setState({ -// newTx: { -// ...this.state.newTx, -// to: event.target.value, -// }, -// }) -// }, -// onBlur: () => { -// this.setErrorsFor('to') -// }, -// onFocus: event => { -// this.clearErrorsFor('to') -// this.state.newTx.to && event.target.select() -// }, -// }), - -// h('datalist#addresses', [ -// // Corresponds to the addresses owned. -// ...Object.entries(identities).map(([key, { address, name }]) => { -// return h('option', { -// value: address, -// label: name, -// key: address, -// }) -// }), -// // Corresponds to previously sent-to addresses. -// ...addressBook.map(({ address, name }) => { -// return h('option', { -// value: address, -// label: name, -// key: address, -// }) -// }), -// ]), - -// this.renderErrorMessage('to'), - -// ]) -// } - -// SendTransactionScreen.prototype.renderAmountInput = function (activeCurrency) { -// return h('div.send-screen-input-wrapper', [ - -// h('div.send-screen-amount-labels', [ -// h('span', 'Amount'), -// h(CurrencyToggle, { -// activeCurrency, -// onClick: (newCurrency) => this.setActiveCurrency(newCurrency), -// }), // holding on icon from design -// ]), - -// h('input.large-input.send-screen-input', { -// placeholder: `0 ${activeCurrency}`, -// type: 'number', -// onChange: (event) => { -// const amountToSend = event.target.value -// ? this.getAmountToSend(event.target.value) -// : '0x0' - -// this.setState({ -// newTx: Object.assign( -// this.state.newTx, -// { -// amount: event.target.value, -// amountToSend: amountToSend, -// } -// ), -// }) -// }, -// onBlur: () => { -// this.setErrorsFor('amount') -// }, -// onFocus: () => this.clearErrorsFor('amount'), -// }), - -// this.renderErrorMessage('amount'), - -// ]) -// } - -// SendTransactionScreen.prototype.renderGasInput = function (gasPrice, gas, activeCurrency, conversionRate, blockGasLimit) { -// return h('div.send-screen-input-wrapper', [ -// this.state.tooltipIsOpen && h(GasTooltip, { -// className: 'send-tooltip', -// gasPrice, -// gasLimit: gas, -// onClose: this.closeTooltip, -// onFeeChange: ({gasLimit, gasPrice}) => { -// this.setState({ -// newTx: { -// ...this.state.newTx, -// gas: gasLimit, -// gasPrice, -// }, -// }) -// }, -// }), - -// h('div.send-screen-gas-labels', [ -// h('span', [ -// h('i.fa.fa-bolt'), -// 'Gas fee:', -// ]), -// h('span', 'What\'s this?'), -// ]), - -// // TODO: handle loading time when switching to USD -// h('div.large-input.send-screen-gas-input', {}, [ -// h(GasFeeDisplay, { -// activeCurrency, -// conversionRate, -// gas, -// gasPrice, -// blockGasLimit, -// }), -// h('div.send-screen-gas-input-customize', { -// onClick: this.toggleTooltip, -// }, [ -// 'Customize', -// ]), -// ]), - -// ]) -// } - -// SendTransactionScreen.prototype.renderMemoInput = function () { -// return h('div.send-screen-input-wrapper', [ -// h('div', 'Transaction memo (optional)'), -// h('input.large-input.send-screen-input', { -// onChange: () => { -// this.setState({ -// newTx: Object.assign( -// this.state.newTx, -// { -// memo: event.target.value, -// } -// ), -// }) -// }, -// }), -// ]) -// } - -// SendTransactionScreen.prototype.render = function () { -// this.persistentFormParentId = 'send-tx-form' - -// const props = this.props -// const { -// warning, -// identities, -// addressBook, -// conversionRate, -// } = props - -// const { -// blockGasLimit, -// newTx, -// activeCurrency, -// isValid, -// } = this.state -// const { gas, gasPrice } = newTx - -// return ( - -// h('div.send-screen-wrapper', [ -// // Main Send token Card -// h('div.send-screen-card', [ - -// h('img.send-eth-icon', { src: '../images/eth_logo.svg' }), - -// h('div.send-screen__title', 'Send'), - -// h('div.send-screen__subtitle', 'Send Ethereum to anyone with an Ethereum account'), - -// this.renderFromInput(this.state.newTx.from, identities), - -// this.renderToInput(this.state.newTx.to, identities, addressBook), - -// this.renderAmountInput(activeCurrency), - -// this.renderGasInput( -// gasPrice || '0x0', -// gas || '0x0', -// activeCurrency, -// conversionRate, -// blockGasLimit -// ), - -// this.renderMemoInput(), - -// this.renderErrorMessage(null, warning), - -// ]), - -// // Buttons underneath card -// h('section.flex-column.flex-center', [ -// h('button.btn-secondary.send-screen__send-button', { -// className: !isValid && 'send-screen__send-button__disabled', -// onClick: (event) => isValid && this.onSubmit(event), -// }, 'Next'), -// h('button.btn-tertiary.send-screen__cancel-button', { -// onClick: this.back, -// }, 'Cancel'), -// ]), -// ]) - -// ) -// } - -// SendTransactionScreen.prototype.toggleTooltip = function () { -// this.setState({ tooltipIsOpen: !this.state.tooltipIsOpen }) -// } - -// SendTransactionScreen.prototype.closeTooltip = function () { -// this.setState({ tooltipIsOpen: false }) -// } - -// SendTransactionScreen.prototype.setActiveCurrency = function (newCurrency) { -// this.setState({ activeCurrency: newCurrency }) -// } - -// SendTransactionScreen.prototype.back = function () { -// var address = this.props.address -// this.props.dispatch(backToAccountDetail(address)) -// } - -// SendTransactionScreen.prototype.validate = function (balance, amountToSend, { to, from }) { -// const sufficientBalance = conversionGreaterThan( -// { -// value: balance, -// fromNumericBase: 'hex', -// }, -// { -// value: amountToSend, -// fromNumericBase: 'hex', -// }, -// ) - -// const amountLessThanZero = conversionGreaterThan( -// { -// value: 0, -// fromNumericBase: 'dec', -// }, -// { -// value: amountToSend, -// fromNumericBase: 'hex', -// }, -// ) - -// const errors = {} - -// if (!sufficientBalance) { -// errors.amount = 'Insufficient funds.' -// } - -// if (amountLessThanZero) { -// errors.amount = 'Can not send negative amounts of ETH.' -// } - -// if (!from) { -// errors.from = 'Required' -// } - -// if (from && !isValidAddress(from)) { -// errors.from = 'Sender address is invalid.' -// } - -// if (!to) { -// errors.to = 'Required' -// } - -// if (to && !isValidAddress(to)) { -// errors.to = 'Recipient address is invalid.' -// } - -// // if (txData && !isHex(stripHexPrefix(txData))) { -// // message = 'Transaction data must be hex string.' -// // return this.props.dispatch(displayWarning(message)) -// // } - -// return { -// isValid: allNull(errors), -// errors, -// } -// } - -// SendTransactionScreen.prototype.getAmountToSend = function (amount) { -// const { activeCurrency } = this.state -// const { conversionRate } = this.props - -// return conversionUtil(amount, { -// fromNumericBase: 'dec', -// toNumericBase: 'hex', -// fromCurrency: activeCurrency, -// toCurrency: 'ETH', -// toDenomination: 'WEI', -// conversionRate, -// invertConversionRate: activeCurrency !== 'ETH', -// }) -// } - -// SendTransactionScreen.prototype.setErrorsFor = function (field) { -// const { balance } = this.props -// const { newTx, errors: previousErrors } = this.state -// const { amountToSend } = newTx - -// const { -// isValid, -// errors: newErrors -// } = this.validate(balance, amountToSend, newTx) - -// const nextErrors = Object.assign({}, previousErrors, { -// [field]: newErrors[field] || null -// }) - -// if (!isValid) { -// this.setState({ -// errors: nextErrors, -// isValid, -// }) -// } -// } - -// SendTransactionScreen.prototype.clearErrorsFor = function (field) { -// const { errors: previousErrors } = this.state -// const nextErrors = Object.assign({}, previousErrors, { -// [field]: null -// }) - -// this.setState({ -// errors: nextErrors, -// isValid: allNull(nextErrors), -// }) -// } - -// SendTransactionScreen.prototype.onSubmit = function (event) { -// event.preventDefault() -// const { warning, balance } = this.props -// const state = this.state || {} - -// const recipient = state.newTx.to -// const sender = state.newTx.from -// const nickname = state.nickname || ' ' - -// // TODO: convert this to hex when created and include it in send -// const txData = state.newTx.memo - -// this.props.dispatch(hideWarning()) - -// this.props.dispatch(addToAddressBook(recipient, nickname)) - -// var txParams = { -// from: this.state.newTx.from, -// to: this.state.newTx.to, - -// value: this.state.newTx.amountToSend, - -// gas: this.state.newTx.gas, -// gasPrice: this.state.newTx.gasPrice, -// } - -// if (recipient) txParams.to = addHexPrefix(recipient) -// if (txData) txParams.data = txData - -// this.props.dispatch(signTx(txParams)) -// } diff --git a/ui/app/settings.js b/ui/app/settings.js index 466f739d5..78ca6c94b 100644 --- a/ui/app/settings.js +++ b/ui/app/settings.js @@ -10,6 +10,7 @@ const TabBar = require('./components/tab-bar') const SimpleDropdown = require('./components/dropdowns/simple-dropdown') const ToggleButton = require('react-toggle-button') const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums +const t = require('../i18n') const getInfuraCurrencyOptions = () => { const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => { @@ -44,8 +45,8 @@ class Settings extends Component { return h('div.settings__tabs', [ h(TabBar, { tabs: [ - { content: 'Settings', key: 'settings' }, - { content: 'Info', key: 'info' }, + { content: t('settings'), key: 'settings' }, + { content: t('info'), key: 'info' }, ], defaultTab: activeTab, tabSelected: key => this.setState({ activeTab: key }), @@ -58,7 +59,7 @@ class Settings extends Component { return h('div.settings__content-row', [ h('div.settings__content-item', [ - h('span', 'Use Blockies Identicon'), + h('span', t('blockiesIdenticon')), ]), h('div.settings__content-item', [ h('div.settings__content-item-col', [ @@ -78,13 +79,13 @@ class Settings extends Component { return h('div.settings__content-row', [ h('div.settings__content-item', [ - h('span', 'Current Conversion'), + h('span', t('currentConversion')), h('span.settings__content-description', `Updated ${Date(conversionDate)}`), ]), h('div.settings__content-item', [ h('div.settings__content-item-col', [ h(SimpleDropdown, { - placeholder: 'Select Currency', + placeholder: t('selectCurrency'), options: getInfuraCurrencyOptions(), selectedOption: currentCurrency, onSelect: newCurrency => setCurrentCurrency(newCurrency), @@ -101,31 +102,31 @@ class Settings extends Component { switch (provider.type) { case 'mainnet': - title = 'Current Network' - value = 'Main Ethereum Network' + title = t('currentNetwork') + value = t('mainnet') color = '#038789' break case 'ropsten': - title = 'Current Network' - value = 'Ropsten Test Network' + title = t('currentNetwork') + value = t('ropsten') color = '#e91550' break case 'kovan': - title = 'Current Network' - value = 'Kovan Test Network' + title = t('currentNetwork') + value = t('kovan') color = '#690496' break case 'rinkeby': - title = 'Current Network' - value = 'Rinkeby Test Network' + title = t('currentNetwork') + value = t('rinkeby') color = '#ebb33f' break default: - title = 'Current RPC' + title = t('currentRpc') value = provider.rpcTarget } @@ -146,12 +147,12 @@ class Settings extends Component { return ( h('div.settings__content-row', [ h('div.settings__content-item', [ - h('span', 'New RPC URL'), + h('span', t('newRPC')), ]), h('div.settings__content-item', [ h('div.settings__content-item-col', [ h('input.settings__input', { - placeholder: 'New RPC URL', + placeholder: t('newRPC'), onChange: event => this.setState({ newRpc: event.target.value }), onKeyPress: event => { if (event.key === 'Enter') { @@ -164,7 +165,7 @@ class Settings extends Component { event.preventDefault() this.validateRpc(this.state.newRpc) }, - }, 'Save'), + }, t('save')), ]), ]), ]) @@ -180,9 +181,9 @@ class Settings extends Component { const appendedRpc = `http://${newRpc}` if (validUrl.isWebUri(appendedRpc)) { - displayWarning('URIs require the appropriate HTTP/HTTPS prefix.') + displayWarning(t('uriErrorMsg')) } else { - displayWarning('Invalid RPC URI') + displayWarning(t('invalidRPC')) } } } @@ -191,25 +192,25 @@ class Settings extends Component { return ( h('div.settings__content-row', [ h('div.settings__content-item', [ - h('div', 'State Logs'), + h('div', t('stateLogs')), h( 'div.settings__content-description', - 'State logs contain your public account addresses and sent transactions.' + t('stateLogsDescription') ), ]), h('div.settings__content-item', [ h('div.settings__content-item-col', [ - h('button.settings__clear-button', { + h('button.btn-primary--lg.settings__button', { onClick (event) { window.logStateString((err, result) => { if (err) { - this.state.dispatch(actions.displayWarning('Error in retrieving state logs.')) + this.state.dispatch(actions.displayWarning(t('stateLogError'))) } else { exportAsFile('MetaMask State Logs.json', result) } }) }, - }, 'Download State Logs'), + }, t('downloadStateLogs')), ]), ]), ]) @@ -221,15 +222,15 @@ class Settings extends Component { return ( h('div.settings__content-row', [ - h('div.settings__content-item', 'Reveal Seed Words'), + h('div.settings__content-item', t('revealSeedWords')), h('div.settings__content-item', [ h('div.settings__content-item-col', [ - h('button.settings__clear-button.settings__clear-button--red', { + h('button.btn-primary--lg.settings__button--red', { onClick (event) { event.preventDefault() revealSeedConfirmation() }, - }, 'Reveal Seed Words'), + }, t('revealSeedWords')), ]), ]), ]) @@ -241,15 +242,15 @@ class Settings extends Component { return ( h('div.settings__content-row', [ - h('div.settings__content-item', 'Use old UI'), + h('div.settings__content-item', t('useOldUI')), h('div.settings__content-item', [ h('div.settings__content-item-col', [ - h('button.settings__clear-button.settings__clear-button--orange', { + h('button.btn-primary--lg.settings__button--orange', { onClick (event) { event.preventDefault() setFeatureFlagToBeta() }, - }, 'Use old UI'), + }, t('useOldUI')), ]), ]), ]) @@ -260,15 +261,15 @@ class Settings extends Component { const { showResetAccountConfirmationModal } = this.props return h('div.settings__content-row', [ - h('div.settings__content-item', 'Reset Account'), + h('div.settings__content-item', t('resetAccount')), h('div.settings__content-item', [ h('div.settings__content-item-col', [ - h('button.settings__clear-button.settings__clear-button--orange', { + h('button.btn-primary--lg.settings__button--orange', { onClick (event) { event.preventDefault() showResetAccountConfirmationModal() }, - }, 'Reset Account'), + }, t('resetAccount')), ]), ]), ]) @@ -303,13 +304,13 @@ class Settings extends Component { renderInfoLinks () { return ( h('div.settings__content-item.settings__content-item--without-height', [ - h('div.settings__info-link-header', 'Links'), + h('div.settings__info-link-header', t('links')), h('div.settings__info-link-item', [ h('a', { href: 'https://metamask.io/privacy.html', target: '_blank', }, [ - h('span.settings__info-link', 'Privacy Policy'), + h('span.settings__info-link', t('privacyMsg')), ]), ]), h('div.settings__info-link-item', [ @@ -317,7 +318,7 @@ class Settings extends Component { href: 'https://metamask.io/terms.html', target: '_blank', }, [ - h('span.settings__info-link', 'Terms of Use'), + h('span.settings__info-link', t('terms')), ]), ]), h('div.settings__info-link-item', [ @@ -325,7 +326,7 @@ class Settings extends Component { href: 'https://metamask.io/attributions.html', target: '_blank', }, [ - h('span.settings__info-link', 'Attributions'), + h('span.settings__info-link', t('attributions')), ]), ]), h('hr.settings__info-separator'), @@ -334,7 +335,7 @@ class Settings extends Component { href: 'https://support.metamask.io', target: '_blank', }, [ - h('span.settings__info-link', 'Visit our Support Center'), + h('span.settings__info-link', t('supportCenter')), ]), ]), h('div.settings__info-link-item', [ @@ -342,7 +343,7 @@ class Settings extends Component { href: 'https://metamask.io/', target: '_blank', }, [ - h('span.settings__info-link', 'Visit our web site'), + h('span.settings__info-link', t('visitWebSite')), ]), ]), h('div.settings__info-link-item', [ @@ -350,7 +351,7 @@ class Settings extends Component { target: '_blank', href: 'mailto:help@metamask.io?subject=Feedback', }, [ - h('span.settings__info-link', 'Email us!'), + h('span.settings__info-link', t('emailUs')), ]), ]), ]) @@ -372,7 +373,7 @@ class Settings extends Component { h('div.settings__info-item', [ h( 'div.settings__info-about', - 'MetaMask is designed and built in California.' + t('builtInCalifornia') ), ]), ]), @@ -445,3 +446,4 @@ const mapDispatchToProps = dispatch => { } module.exports = connect(mapStateToProps, mapDispatchToProps)(Settings) + diff --git a/ui/app/template.js b/ui/app/template.js deleted file mode 100644 index d15b30fd2..000000000 --- a/ui/app/template.js +++ /dev/null @@ -1,30 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') -const connect = require('react-redux').connect - -module.exports = connect(mapStateToProps)(COMPONENTNAME) - -function mapStateToProps (state) { - return {} -} - -inherits(COMPONENTNAME, Component) -function COMPONENTNAME () { - Component.call(this) -} - -COMPONENTNAME.prototype.render = function () { - const props = this.props - - return ( - h('div', { - style: { - background: 'blue', - }, - }, [ - `Hello, ${props.sender}`, - ]) - ) -} - diff --git a/ui/app/token-tracker.js b/ui/app/token-tracker.js deleted file mode 100644 index e69de29bb..000000000 --- a/ui/app/token-tracker.js +++ /dev/null diff --git a/ui/app/unlock.js b/ui/app/unlock.js index ac97d04d0..322808619 100644 --- a/ui/app/unlock.js +++ b/ui/app/unlock.js @@ -67,7 +67,7 @@ UnlockScreen.prototype.render = function () { style: { margin: 10, }, - }, 'Log In'), + }, t('login')), h('p.pointer', { onClick: () => { @@ -81,7 +81,7 @@ UnlockScreen.prototype.render = function () { color: 'rgb(247, 134, 28)', textDecoration: 'underline', }, - }, 'Restore from seed phrase'), + }, t('restoreFromSeed')), h('p.pointer', { onClick: () => { @@ -94,7 +94,7 @@ UnlockScreen.prototype.render = function () { textDecoration: 'underline', marginTop: '32px', }, - }, 'Use classic interface'), + }, t('classicInterface')), ]) ) } diff --git a/ui/lib/contract-namer.js b/ui/lib/contract-namer.js deleted file mode 100644 index f05e770cc..000000000 --- a/ui/lib/contract-namer.js +++ /dev/null @@ -1,33 +0,0 @@ -/* CONTRACT NAMER - * - * Takes an address, - * Returns a nicname if we have one stored, - * otherwise returns null. - */ - -const contractMap = require('eth-contract-metadata') -const ethUtil = require('ethereumjs-util') - -module.exports = function (addr, identities = {}) { - const checksummed = ethUtil.toChecksumAddress(addr) - if (contractMap[checksummed] && contractMap[checksummed].name) { - return contractMap[checksummed].name - } - - const address = addr.toLowerCase() - const ids = hashFromIdentities(identities) - return addrFromHash(address, ids) -} - -function hashFromIdentities (identities) { - const result = {} - for (const key in identities) { - result[key] = identities[key].name - } - return result -} - -function addrFromHash (addr, hash) { - const address = addr.toLowerCase() - return hash[address] || null -} |