diff options
Merge tag 'v4.5.5'
# Conflicts:
# app/_locales/ja/messages.json
# package-lock.json
messages.jsonのローカライズ
Diffstat (limited to 'app')
52 files changed, 1145 insertions, 561 deletions
diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 0bdce516c..1d2619672 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -14,9 +14,15 @@ "address": { "message": "Adresse" }, + "addCustomToken": { + "message": "Eigenen Token hinzufügen" + }, "addToken": { "message": "Token hinzufügen" }, + "addTokens": { + "message": "Token hinzufügen" + }, "amount": { "message": "Betrag" }, @@ -31,9 +37,15 @@ "message": "MetaMask", "description": "Der Name der Erweiterung" }, + "approved": { + "message": "Genehmigt" + }, "attemptingConnect": { "message": "Versuch mit der Blockchain zu verbinden." }, + "attributions": { + "message": "Was wir verwenden" + }, "available": { "message": "Verfügbar" }, @@ -43,6 +55,9 @@ "balance": { "message": "Guthaben:" }, + "balances": { + "message": "Deine Guthaben" + }, "balanceIsInsufficientGas": { "message": "Guthaben unzureichend für den aktuellen gesamten Gasbetrag" }, @@ -69,7 +84,7 @@ "message": "Auf Coinbase kaufen" }, "buyCoinbaseExplainer": { - "message": "Coinbase ist die weltweit bekannteste Möglichkeit bitcoin, ethereum und litecoin zu kaufen und verkaufen." + "message": "Coinbase ist die weltweit bekannteste Art und Weise um bitcoin, ethereum und litecoin zu kaufen und verkaufen." }, "ok": { "message": "Ok" @@ -105,7 +120,7 @@ "message": "Zu Coinbase fortfahren" }, "contractDeployment": { - "message": "Smart Contract ausführen" + "message": "Smart Contract Ausführung" }, "conversionProgress": { "message": "Umtausch in Arbeit" @@ -148,7 +163,7 @@ "description": "Börsentyp (Kryptowährungen)" }, "currentConversion": { - "message": "Aktueller Umtausch" + "message": "Aktuelle Tauschwährung" }, "currentNetwork": { "message": "Aktuelles Netzwerk" @@ -185,7 +200,7 @@ "description": "Teilt dem Benutzer mit welchen Token er beim Einzahlen mit Shapeshift ausgewählt hat" }, "depositEth": { - "message": "Eth einzahlen" + "message": "Eth kaufen" }, "depositEther": { "message": "Ether einzahlen" @@ -217,7 +232,7 @@ "done": { "message": "Fertig" }, - "downloadStatelogs": { + "downloadStateLogs": { "message": "Statelogs herunterladen" }, "dropped": { @@ -274,7 +289,7 @@ "message": "Folge uns auf Twitter" }, "from": { - "message": "von" + "message": "Von" }, "fromToSame": { "message": "Ziel- und Ursprungsadresse dürfen nicht identisch sein" @@ -347,14 +362,14 @@ "message": "Es erlaubt dir ether & Token zu halten und dient dir als Verbindung zu dezentralisierten Applikationen." }, "import": { - "message": "Import", + "message": "Importieren", "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." + "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" @@ -369,7 +384,7 @@ "infoHelp": { "message": "Info & Hilfe" }, - "insufficientFunds": { + "insufficientFunds": { "message": "Nicht genügend Guthaben." }, "insufficientTokens": { @@ -441,10 +456,10 @@ "message": "Frei" }, "loweCaseWords": { - "message": "Die Wörter der Seed Wörterfolgen sind alle kleingeschrieben" + "message": "Die Wörter der Seed-Wörterfolgen sind alle kleingeschrieben" }, "mainnet": { - "message": "Ethereum Hauptnetzwerk (Main Net)" + "message": "Ethereum Main Net" }, "message": { "message": "Nachricht" @@ -541,7 +556,7 @@ "description": "Für den Import eine Accounts mit Hilfe eines Private Keys" }, "pasteSeed": { - "message": "Füge deine Seed Wörterfolge hier ein!" + "message": "Füge deine Seed-Wörterfolge hier ein!" }, "personalAddressDetected": { "message": "Personalisierte Adresse identifiziert. Bitte füge die Token Contract Adresse ein." @@ -557,7 +572,7 @@ "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." + "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" @@ -566,7 +581,7 @@ "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." + "message": "Du kannst diesen Token immer erneut hinzufügen, indem du in den Menüpunkt \"Token hinzufügen\" in den Einstellungen deines Accounts gehst." }, "readMore": { "message": "Hier mehr erfahren." @@ -590,7 +605,7 @@ "message": "Account zurücksetzten" }, "restoreFromSeed": { - "message": "Mit Hilfe der Seed Wörterfolge wiederherstellen." + "message": "Mit Hilfe der Seed-Wörterfolge wiederherstellen." }, "restoreVault": { "message": "Vault wiederherstellen" @@ -605,13 +620,13 @@ "message": "Wallet Seed" }, "revealSeedWords": { - "message": "Seed Wörterfolge anzeigen" + "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." + "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" + "message": "Rückgängig machen" }, "rinkeby": { "message": "Rinkeby Testnetzwerk" @@ -623,7 +638,7 @@ "message": "Aktueller RPC" }, "connectingToMainnet": { - "message": "Verbinde zum Ethereum Hauptnetzwerk (Main Net)" + "message": "Verbinde zum Ethereum Main Net" }, "connectingToRopsten": { "message": " Verbinde zum Ropsten Testnetzwerk" @@ -649,7 +664,7 @@ "description": "Prozess des Exportieren eines Accounts" }, "saveSeedAsFile": { - "message": "Seed Wörterfolge als Datei speichern" + "message": "Seed-Wörterfolge als Datei speichern" }, "search": { "message": "Suche" @@ -661,7 +676,7 @@ "message": "Neues Passwort (min. 8 Zeichen)" }, "seedPhraseReq": { - "message": "Seed Wörterfolgen bestehen aus 12 Wörtern" + "message": "Seed-Wörterfolgen bestehen aus 12 Wörtern" }, "select": { "message": "Auswählen" @@ -685,7 +700,7 @@ "message": "Token senden" }, "onlySendToEtherAddress": { - "message": "ETH nur zu einer Ethereum Adresse senden." + "message": "ETH unbedingt nur zu einer Ethereum Adresse senden." }, "sendTokensAnywhere": { "message": "Token zu einer beliebigen Person mit einem Ethereumaccount senden" @@ -742,7 +757,7 @@ "message": "Einreichen" }, "submitted": { - "message": "Eingereicht" + "message": "Abgeschickt" }, "supportCenter": { "message": "Gehe zu unserem Support Center" @@ -782,7 +797,7 @@ "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." + "message": "Behalte die Token die du mit deinem MetaMask Account gekauft hast im Blick. Wenn du Token mit einem anderen Account gekauft hast, werden diese hier nicht angezeigt." }, "total": { "message": "Gesamt" @@ -853,7 +868,7 @@ "message": " Account einsehen" }, "visitWebSite": { - "message": "Gehe zu unsere Webseite" + "message": "Gehe zu unserer Webseite" }, "warning": { "message": "Warnung" diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 0bfa992b4..b372326ee 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -56,7 +56,7 @@ "message": "Balance:" }, "balances": { - "message": "Your balances" + "message": "Token balance(s)" }, "balanceIsInsufficientGas": { "message": "Insufficient balance for current gas total" @@ -185,7 +185,7 @@ }, "decimal": { "message": "Decimals of Precision" - }, + }, "defaultNetwork": { "message": "The default network for Ether transactions is Main Net." }, @@ -235,7 +235,7 @@ "done": { "message": "Done" }, - "downloadStatelogs": { + "downloadStateLogs": { "message": "Download State Logs" }, "dropped": { @@ -671,6 +671,12 @@ "save": { "message": "Save" }, + "reprice_title": { + "message": "Reprice Transaction" + }, + "reprice_subtitle": { + "message": "Increase your gas price to attempt to overwrite and speed up your transaction" + }, "saveAsFile": { "message": "Save as File", "description": "Account export process" @@ -820,6 +826,9 @@ "transactions": { "message": "transactions" }, + "transactionError": { + "message": "Transaction Error. Exception thrown in contract code." + }, "transactionMemo": { "message": "Transaction memo (optional)" }, @@ -884,7 +893,7 @@ }, "visitWebSite": { "message": "Visit our web site" - }, + }, "warning": { "message": "Warning" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index fa28b09da..f287674f1 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -247,7 +247,7 @@ "done": { "message": "Completo" }, - "downloadStatelogs": { + "downloadStateLogs": { "message": "Descargar logs de estado" }, "dropped": { diff --git a/app/_locales/hn/messages.json b/app/_locales/hn/messages.json index 3703faa13..323f4b4b3 100644 --- a/app/_locales/hn/messages.json +++ b/app/_locales/hn/messages.json @@ -223,7 +223,7 @@ "done": { "message": "संपन्न" }, - "downloadStatelogs": { + "downloadStateLogs": { "message": "राज्य लॉग डाउनलोड करें" }, "edit": { diff --git a/app/_locales/index.json b/app/_locales/index.json new file mode 100644 index 000000000..c085deb72 --- /dev/null +++ b/app/_locales/index.json @@ -0,0 +1,19 @@ +[ + { "code": "de", "name": "German" }, + { "code": "en", "name": "English" }, + { "code": "es", "name": "Spanish" }, + { "code": "fr", "name": "French" }, + { "code": "hn", "name": "Hindi" }, + { "code": "it", "name": "Italian" }, + { "code": "ja", "name": "Japanese" }, + { "code": "ko", "name": "Korean" }, + { "code": "nl", "name": "Dutch" }, + { "code": "ph", "name": "Tagalog" }, + { "code": "pt", "name": "Portuguese" }, + { "code": "ru", "name": "Russian" }, + { "code": "sl", "name": "Slovenian" }, + { "code": "th", "name": "Thai" }, + { "code": "vi", "name": "Vietnamese" }, + { "code": "zh_CN", "name": "Mandarin" }, + { "code": "zh_TW", "name": "Taiwanese" } +] diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index f54ef98ca..ef3ed4025 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -223,7 +223,7 @@ "done": { "message": "Finito" }, - "downloadStatelogs": { + "downloadStateLogs": { "message": "Scarica i log di Stato" }, "edit": { diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 2c4f271b7..3a664ec00 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -49,6 +49,9 @@ "balance": { "message": "残高:" }, + "balances": { + "message": "トークン残高" + }, "balanceIsInsufficientGas": { "message": "現在のガス総量に対して残高が不足しています" }, @@ -160,20 +163,20 @@ "message": "DENとは、あなたのパスワードが暗号化されたMetaMask内のストレージです。" }, "deposit": { - "message": "お振込み" + "message": "振込" }, "depositBTC": { "message": "BTCを下記のアドレスへ振込んでください:" }, "depositCoin": { - "message": "あなたの $1を下記のアドレスへ振込んでください", + "message": "$1を下記のアドレスへ振込んでください", "description": "Tells the user what coin they have selected to deposit with shapeshift" }, "depositEth": { "message": "ETHを入金" }, "depositEther": { - "message": "Etherを入金" + "message": "Etherを振込" }, "depositFiat": { "message": "法定通貨でデポジット" @@ -215,7 +218,7 @@ "message": "パスワードを入力" }, "etherscanView": { - "message": "Etherscanでアカウントを参照" + "message": "Etherscanでアカウントを確認" }, "exchangeRate": { "message": "交換レート" @@ -293,7 +296,7 @@ "message": "トークンを隠す" }, "hideTokenPrompt": { - "message": "トークンを隠しますか??" + "message": "トークンを隠しますか?" }, "howToDeposit": { "message": "どのようにEtherをデポジットしますか?" @@ -306,7 +309,7 @@ "message": "アカウントのインポート" }, "importAccountMsg": { - "message":"追加したアカウントはMetaMaskのアカウントシードフレーズとは関連付けられません。インポートしたアカウントについての詳細は" + "message":"追加したアカウントはMetaMaskのアカウントパスフレーズとは関連付けられません。インポートしたアカウントについての詳細は" }, "importAnAccount": { "message": "アカウントをインポート" @@ -341,7 +344,7 @@ "description": "format for importing an account" }, "keepTrackTokens": { - "message": "MetaMaskアカウントで入手したトークンを追跡でできます。" + "message": "MetaMaskアカウントで入手したトークンを検索できます。" }, "kovan": { "message": "Kovanテストネットワーク" @@ -386,6 +389,9 @@ "myAccounts": { "message": "マイアカウント" }, + "mustSelectOne": { + "message": "一つ以上のトークンを選択してください。" + }, "needEtherInWallet": { "message": "MetaMaskで分散型アプリケーションを使用するためには、このウォレットにEtherが必要です。" }, @@ -426,7 +432,7 @@ "message": "この名前にはアドレスが設定されていません。" }, "noDeposits": { - "message": "お振込みがありません。" + "message": "振込みがありません。" }, "noTransactionHistory": { "message": "トランザクション履歴がありません。" @@ -460,11 +466,14 @@ "description": "For importing an account from a private key" }, "pasteSeed": { - "message": "シードをここにペーストして下さい!" + "message": "パスフレーズをここにペーストして下さい!" }, "pleaseReviewTransaction": { "message": "トランザクションを確認して下さい。" }, + "popularTokens": { + "message": "人気のトークン" + }, "privateKey": { "message": "秘密鍵", "description": "select this type of file to use to import an account" @@ -485,13 +494,13 @@ "message": "もっと読む" }, "receive": { - "message": "お受取り" + "message": "受取" }, "recipientAddress": { "message": "受取人アドレス" }, "refundAddress": { - "message": "お受取りアドレス" + "message": "受取アドレス" }, "rejected": { "message": "拒否されました" @@ -502,12 +511,18 @@ "restoreFromSeed": { "message": "パスフレーズから復元する" }, + "restoreVault": { + "message": "ウォレットを復元する" + }, "required": { "message": "必要です。" }, "retryWithMoreGas": { "message": "より高いガスプライスで再度試して下さい。" }, + "walletSeed": { + "message": "ウォレットのパスフレーズ" + }, "revealSeedWords": { "message": "パスフレーズを表示" }, @@ -534,6 +549,9 @@ "selectService": { "message": "サービスを選択" }, + "selectType": { + "message": "キーの種類" + }, "send": { "message": "送信" }, @@ -555,8 +573,11 @@ "settings": { "message": "設定" }, + "info": { + "message": "情報" + }, "shapeshiftBuy": { - "message": "Shapeshiftで取引" + "message": "Shapeshiftで交換" }, "showPrivateKeys": { "message": "秘密鍵を表示" @@ -616,6 +637,9 @@ "total": { "message": "合計" }, + "transactions": { + "message": "トランザクション" + }, "transactionMemo": { "message": "トランザクションメモ (オプション)" }, diff --git a/app/_locales/nl/messages.json b/app/_locales/nl/messages.json index aacb81fee..38289f602 100644 --- a/app/_locales/nl/messages.json +++ b/app/_locales/nl/messages.json @@ -223,7 +223,7 @@ "done": { "message": "Gedaan" }, - "downloadStatelogs": { + "downloadStateLogs": { "message": "Staatslogboeken downloaden" }, "edit": { @@ -299,7 +299,7 @@ "message": "De gaslimiet moet minstens 21000 zijn" }, "generatingSeed": { - "message": "Zaad produceren ..." + "message": "Back-up woorden produceren ..." }, "gasPrice": { "message": "Gasprijs (GWEI)" @@ -432,7 +432,7 @@ "message": "Los" }, "loweCaseWords": { - "message": "zaadwoorden hebben alleen kleine letters" + "message": "back-up woorden hebben alleen kleine letters" }, "mainnet": { "message": "belangrijkste ethereum-netwerk" @@ -532,7 +532,7 @@ "description": "Voor het importeren van een account vanaf een privésleutel" }, "pasteSeed": { - "message": "Plak je zaadzin hier!" + "message": "Plak je back-up woorden hier!" }, "personalAddressDetected": { "message": "Persoonlijk adres gedetecteerd. Voer het tokencontractadres in." @@ -581,7 +581,7 @@ "message": "Account opnieuw instellen" }, "restoreFromSeed": { - "message": "Herstel van zaaduitdrukking" + "message": "Herstel vanuit back-up woorden" }, "required": { "message": "Verplicht" @@ -590,10 +590,10 @@ "message": "Probeer hier opnieuw met een hogere gasprijs" }, "revealSeedWords": { - "message": "Onthul zaadwoorden" + "message": "Onthul back-up woorden" }, "revealSeedWordsWarning": { - "message": "Herstel je zaadwoorden niet op een openbare plaats! Deze woorden kunnen worden gebruikt om al uw accounts te stelen." + "message": "Zorg dat je back-up woorden niet op een openbare plaats bekijkt! Deze woorden kunnen worden gebruikt om al uw accounts opnieuw te genereren (en dus uw account te stelen)." }, "revert": { "message": "terugkeren" @@ -616,7 +616,7 @@ "description": "Account export proces" }, "saveSeedAsFile": { - "message": "Bewaar zaadwoorden als bestand" + "message": "Bewaar back-up woorden als bestand" }, "search": { "message": "Zoeken" @@ -625,7 +625,7 @@ "message": "Voer hier je geheime twaalfwoordfrase in om je kluis te herstellen." }, "seedPhraseReq": { - "message": "zaadzinnen zijn 12 woorden lang" + "message": "Back-up woorden zijn 12 woorden lang" }, "select": { "message": "kiezen" diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index c9eb178f9..e770392d0 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -223,7 +223,7 @@ "done": { "message": "Finalizado" }, - "downloadStatelogs": { + "downloadStateLogs": { "message": "Descarregar Registos de Estado" }, "edit": { diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index e3a1935f5..b43251064 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -3,13 +3,13 @@ "message": "Принять" }, "account": { - "message": "Аккаунт" + "message": "Счет" }, "accountDetails": { - "message": "Детали Аккаунта" + "message": "Детали счета" }, "accountName": { - "message": "Имя Пользователя" + "message": "Название счета" }, "address": { "message": "Адрес" @@ -21,13 +21,13 @@ "message": "Добавить токен" }, "addTokens": { - "message": "Добавить Токены" + "message": "Добавить токены" }, "amount": { - "message": "Количество" + "message": "Сумма" }, "amountPlusGas": { - "message": "Количество + газ" + "message": "Сумма + газ" }, "appDescription": { "message": "Расширение браузера для Ethereum", @@ -37,11 +37,14 @@ "message": "MetaMask", "description": "The name of the application" }, + "approved": { + "message": "Одобрена" + }, "attemptingConnect": { "message": "Попытка подключиться к блокчейн сети." }, "attributions": { - "message": "Опознания" + "message": "Атрибуция" }, "available": { "message": "Доступный" @@ -53,13 +56,13 @@ "message": "Баланс:" }, "balances": { - "message": "Ваши балансы" + "message": "Ваш баланс" }, "balanceIsInsufficientGas": { "message": "Недостаточный баланс для текущего объема газа" }, "beta": { - "message": "БЕТА" + "message": "BETA" }, "betweenMinAndMax": { "message": "должно быть больше или равно $1 и меньше или равно $2.", @@ -69,10 +72,10 @@ "message": "Использовать Blockies Identicon" }, "borrowDharma": { - "message": "Заимствовать с Dharma (бета)" + "message": "Взять в долг на Dharma (Beta)" }, "builtInCalifornia": { - "message": "MetaMask спроектирован и построен в Калифорнии." + "message": "MetaMask спроектирован и разработан в Калифорнии." }, "buy": { "message": "Купить" @@ -81,7 +84,10 @@ "message": "Купить на Coinbase" }, "buyCoinbaseExplainer": { - "message": "Coinbase - самый популярный в мире способ купить и продать биткойн, ethereum и litecoin." + "message": "Биржа Coinbase – это наиболее популярный способ купить или продать bitcoin, ethereum и litecoin." + }, + "ok": { + "message": "ОК" }, "cancel": { "message": "Отмена" @@ -95,14 +101,17 @@ "confirm": { "message": "Подтвердить" }, + "confirmed": { + "message": "Подтверждена" + }, "confirmContract": { - "message": "Подтвердить Контракт" + "message": "Подтвердить контракт" }, "confirmPassword": { - "message": "Подтвердите Пароль" + "message": "Подтвердите пароль" }, "confirmTransaction": { - "message": "Подтвердить Транзакцию" + "message": "Подтвердить транзакцию" }, "continue": { "message": "Продолжить" @@ -114,7 +123,7 @@ "message": "Развертывание контракта" }, "conversionProgress": { - "message": "Выполняется конверсия" + "message": "Выполняется конвертация" }, "copiedButton": { "message": "Скопировано" @@ -126,7 +135,7 @@ "message": "Скопировано!" }, "copiedSafe": { - "message": "Я скопировал его где-то в безопасности" + "message": "Я скопировал это в безопасное место" }, "copy": { "message": "Скопировать" @@ -138,29 +147,32 @@ "message": " Скопировать " }, "copyPrivateKey": { - "message": "Это ваш личный ключ (нажмите, чтобы скопировать)" + "message": "Это ваш закрытый ключ (нажмите, чтобы скопировать)" }, "create": { "message": "Создать" }, "createAccount": { - "message": "Регистрация" + "message": "Создать счет" }, "createDen": { "message": "Создать" }, "crypto": { - "message": "Крипто", + "message": "Криптовалюта", "description": "Exchange type (cryptocurrencies)" }, "currentConversion": { - "message": "Текущая конверсия" + "message": "Текущая конвертация" }, "currentNetwork": { "message": "Текущая сеть" }, "customGas": { - "message": "Настроить Газ" + "message": "Настроить газ" + }, + "customToken": { + "message": "Пользовательский токен" }, "customize": { "message": "Настроить" @@ -169,112 +181,115 @@ "message": "Пользовательский RPC" }, "decimalsMustZerotoTen": { - "message": "Десятичные числа должны быть не менее 0, и не более 36." + "message": "Количество десятичных разрядов должно быть минимум 0 и максимум 36." }, "decimal": { - "message": "Десятичные значения точности" + "message": "Количество десятичных разрядов" }, "defaultNetwork": { - "message": "Сеть по умолчанию для транзакций Ether - это Main Net." + "message": "Основная сеть Ethereum – это сеть по умолчанию для Ether транзакций." }, "denExplainer": { - "message": "Ваш DEN - это ваше зашифрованное паролем хранилище в MetaMask." + "message": "DEN – это зашифрованное паролем хранилище внутри MetaMask." }, "deposit": { - "message": "Депозит" + "message": "Пополнить" }, "depositBTC": { - "message": "Депозит BTC по адресу:" + "message": "Отправьте ваш BTC на адрес ниже:" }, "depositCoin": { - "message": "Депозит $1 по указанному ниже адресу", + "message": "Отправьте ваш $1 на адрес ниже", "description": "Tells the user what coin they have selected to deposit with shapeshift" }, "depositEth": { - "message": "Депозит Eth" + "message": "Пополнить Eth" }, "depositEther": { - "message": "Депозит Эфир" + "message": "Пополнить Ether" }, "depositFiat": { - "message": "Депозит с деньгами" + "message": "Пополнить деньгами" }, "depositFromAccount": { - "message": "Депозит с другого счета" + "message": "Пополнить с другого счета" }, "depositShapeShift": { - "message": "Депозит с ShapeShift" + "message": "Пополнить через ShapeShift" }, "depositShapeShiftExplainer": { - "message": "Если у вас есть другие крипторесурсы, вы можете торговать и вносить Эфир непосредственно в кошелек MetaMask. Нет необходимости в аккаунте." + "message": "Если у вас есть другие криптовалюты, вы можете торговать и пополнять Ether напрямую в ваш MetaMask кошелек. Нет необходимости в счете." }, "details": { "message": "Детали" }, "directDeposit": { - "message": "Прямой Депозит" + "message": "Прямое пополнение" }, "directDepositEther": { - "message": "Прямой Депозит Эфира" + "message": "Прямое пополнение Ether" }, "directDepositEtherExplainer": { - "message": "Если у вас уже есть Эфир, самый быстрый способ получить Эфир в вашем новом кошельке это прямым депозитом." + "message": "Если у вас уже есть Ether, то самый быстрый способ получить Ether в ваш новый кошелек – это прямое пополнение." }, "done": { "message": "Готово" }, - "downloadStatelogs": { - "message": "Загрузить логи статус" + "downloadStateLogs": { + "message": "Скачать журнал состояния" + }, + "dropped": { + "message": "Отброшена" }, "edit": { "message": "Редактировать" }, "editAccountName": { - "message": "Изменить Имя Аккаунта" + "message": "Редактировать название счета" }, "emailUs": { "message": "Свяжитесь с нами по электронной почте!" }, "encryptNewDen": { - "message": "Шифруйте новый DEN" + "message": "Зашифровать ваш новый DEN" }, "enterPassword": { "message": "Введите пароль" }, "enterPasswordConfirm": { - "message": "Введите свой пароль для подтверждения" + "message": "Введите ваш пароль для подтверждения" }, "etherscanView": { - "message": "Просмотреть аккаунт на Etherscan" + "message": "Просмотреть счет на Etherscan" }, "exchangeRate": { - "message": "Обменный Курс" + "message": "Обменный курс" }, "exportPrivateKey": { - "message": "Экспорт закрытого ключа" + "message": "Экспортировать закрытый ключ" }, "exportPrivateKeyWarning": { - "message": "Экспорт секретных ключей на свой страх и риск." + "message": "Вы экспортируете закрытые ключи на свой страх и риск." }, "failed": { - "message": "Не смогли" + "message": "Неудачна" }, "fiat": { - "message": "Бумажные деньги", + "message": "Валюта", "description": "Exchange type" }, "fileImportFail": { - "message": "Ошибка импорта файлов? Кликните сюда!", + "message": "Не работает импорт файла? Нажмите тут!", "description": "Helps user import their account from a JSON file" }, "followTwitter": { - "message": "Следуйте за нами на Twitter" + "message": "Читайте нас в Twitter" }, "from": { - "message": "Из" + "message": "Отправитель" }, "fromToSame": { - "message": "От и до адреса не могут быть одинаковым" + "message": "Адрес отправителя и получателя не могут быть одинаковыми" }, "fromShapeShift": { "message": "Из ShapeShift" @@ -284,37 +299,37 @@ "description": "Short indication of gas cost" }, "gasFee": { - "message": "Плата за Газ" + "message": "Комиссия за газ" }, "gasLimit": { - "message": "Газовый Предел" + "message": "Лимит газа" }, "gasLimitCalculation": { - "message": "Мы рассчитываем предполагаемый предел газа на основе коэффициентов успешности сети." + "message": "Мы расчитываем предлагаемый лимит газа на основании успешных ставок в сети." }, "gasLimitRequired": { - "message": "Требуется ограничение на Газ" + "message": "Установите лимит газа" }, "gasLimitTooLow": { - "message": "Предел газа должен быть не менее 21000" + "message": "Лимит газа должен быть как минимум 21000" }, "generatingSeed": { - "message": "Создание Семян ..." + "message": "Генерируем фразу..." }, "gasPrice": { - "message": "Цена на Газ (GWEI)" + "message": "Цена за газ (GWEI)" }, "gasPriceCalculation": { - "message": "Мы вычисляем предлагаемые цены на газ на основе коэффициентов успеха сети." + "message": "Мы расчитываем предлагаемые цены за газ на основании успешных ставок в сети." }, "gasPriceRequired": { - "message": "Требуется цена на Газ" + "message": "Установите стоимость газа" }, "getEther": { - "message": "Получить Эфир" + "message": "Получить Ether" }, "getEtherFromFaucet": { - "message": "Получите Эфир из крана $1", + "message": "Получить Ether из крана для $1", "description": "Displays network name for Ether faucet" }, "greaterThanMin": { @@ -322,14 +337,14 @@ "description": "helper for inputting hex as decimal input" }, "here": { - "message": "здесь", + "message": "тут", "description": "as in -click here- for more information (goes with troubleTokenBalances)" }, "hereList": { - "message": "Вот список !!!!" + "message": "Вот список!!!!" }, "hide": { - "message": "Спрятать" + "message": "Скрыть" }, "hideToken": { "message": "Скрыть токен" @@ -338,33 +353,33 @@ "message": "Скрыть токен?" }, "howToDeposit": { - "message": "Как бы вы хотели поместить Эфир?" + "message": "Как бы вы хотели пополнить Ether?" }, "holdEther": { - "message": "Это позволяет вам использовать эфир и токены и служит мостом для децентрализованных приложений." + "message": "Позволяет вам хранить ether и токены и служит в качестве моста в децентрализированные приложения." }, "import": { "message": "Импортировать", "description": "Button to import an account from a selected file" }, "importAccount": { - "message": "Импорт Аккаунта" + "message": "Импортировать счет" }, "importAccountMsg": { - "message": " Импортированные аккаунты не будут связаны с вашей первоначально созданным аккаунтом MetaMask. Подробнее о импортированных аккаунтах " + "message":" Импортированные счета не будут ассоциированы с вашей ключевой фразой, созданной MetaMask. Узнать больше про импорт счетов " }, "importAnAccount": { "message": "Импортировать аккаунт" }, "importDen": { - "message": "Импорт существующих DEN" + "message": "Импортировать существующий DEN" }, "imported": { "message": "Импортирован", "description": "status showing that an account has been fully loaded into the keyring" }, "infoHelp": { - "message": "Информация и Помощь" + "message": "Информация и помощь" }, "insufficientFunds": { "message": "Недостаточно средств." @@ -373,35 +388,44 @@ "message": "Недостаточно токенов." }, "invalidAddress": { - "message": "Недействительный адрес" + "message": "Неверный адрес" }, "invalidAddressRecipient": { - "message": "Недопустимый адрес получателя." + "message": "Неверный адрес получателя" }, "invalidGasParams": { - "message": "Недопустимые параметры Газа" + "message": "Неверные параметры газа" }, "invalidInput": { - "message": "Неправильный ввод." + "message": "Неверный ввод." }, "invalidRequest": { - "message": "Неверный Запрос" + "message": "Неверный запрос" }, "invalidRPC": { - "message": "Недопустимый URI RPC" + "message": "Неверный RPC URI" }, "jsonFail": { - "message": "Что-то пошло не так. Убедитесь, что ваш файл JSON правильно отформатирован." + "message": "Что-то пошло не так. Убедитесь, что ваш JSON файл правильно отформатирован." }, "jsonFile": { - "message": "Файл JSON", + "message": "JSON файл", "description": "format for importing an account" }, + "keepTrackTokens": { + "message": "Следите за купленными вами токенами с помощью аккаунта MetaMask." + }, "kovan": { - "message": "Kovan тестовая сеть" + "message": "Тестовая сеть Kovan" }, "knowledgeDataBase": { - "message": "Посетите нашу базу знаний" + "message": "Посмотрите нашу Базу Знаний" + }, + "max": { + "message": "Максимум" + }, + "learnMore": { + "message": "Узнать больше." }, "lessThanMax": { "message": "должно быть меньше или равно $1.", @@ -410,29 +434,32 @@ "likeToAddTokens": { "message": "Вы хотите добавить эти токены?" }, + "links": { + "message": "Ссылки" + }, "limit": { - "message": "Предел" + "message": "Лимит" }, "loading": { "message": "Загрузка..." }, "loadingTokens": { - "message": "Загрузка токенов ..." + "message": "Загрузка токенов..." }, "localhost": { - "message": "Локальный адрес 8545" + "message": "Localhost 8545" }, "login": { - "message": "Авторизоваться" + "message": "Вход" }, "logout": { - "message": "Выйти" + "message": "Выход" }, "loose": { - "message": "Рыхлый" + "message": "Несвязанный" }, "loweCaseWords": { - "message": "семенные слова имеют только символы нижнего регистра" + "message": "ключевая фраза может содержать только символы нижнего регистра" }, "mainnet": { "message": "Основная сеть Ethereum" @@ -441,19 +468,19 @@ "message": "Сообщение" }, "metamaskDescription": { - "message": "MetaMask - это безопасное хранилище для Ethereum." + "message": "MetaMask – безопасный кошелек для Ethereum." }, "min": { "message": "Минимум" }, "myAccounts": { - "message": "Мои Аккаунты" + "message": "Мои счета" }, "mustSelectOne": { - "message": "Необходимо выбрать не менее 1 токена." + "message": "Необходимо выбрать как минимум 1 токен." }, "needEtherInWallet": { - "message": "Чтобы взаимодействовать с децентрализованными приложениями с помощью MetaMask, вам понадобится Эфир в вашем кошельке." + "message": "Для взаимодействия с децентрализованными приложениями с помощью MetaMask нужен Ether в вашем кошельке." }, "needImportFile": { "message": "Вы должны выбрать файл для импорта.", @@ -464,60 +491,60 @@ "description": "Password and file needed to import an account" }, "negativeETH": { - "message": "Невозможно отправить отрицательные количества ETH." + "message": "Невозможно отправить отрицательную сумму ETH." }, "networks": { "message": "Сети" }, "newAccount": { - "message": "Новый Аккаунт" + "message": "Новый счет" }, "newAccountNumberName": { - "message": "Аккаунт $1", + "message": "Счет $1", "description": "Default name of next account to be created on create account screen" }, "newContract": { - "message": "Новый Контракт" + "message": "Новый контракт" }, "newPassword": { "message": "Новый пароль (мин. 8 символов)" }, "newRecipient": { - "message": "Новый Получатель" + "message": "Новый получатель" }, "newRPC": { - "message": "Новый URL-адрес RPC" + "message": "Новый RPC URL" }, "next": { "message": "Далее" }, "noAddressForName": { - "message": "Для этого имени не задан адрес." + "message": "Дла этого названия не установлен адрес." }, "noDeposits": { - "message": "Не было получено никаких депозитов" + "message": "Пополнения не получены" }, "noTransactionHistory": { "message": "Нет истории транзакций." }, "noTransactions": { - "message": "Нет Транзакций" + "message": "Нет транзакций" }, "notStarted": { - "message": "Не Начался" + "message": "Не запущен" }, "oldUI": { - "message": "Старый Интерфейс" + "message": "Старая версия интерфейса" }, "oldUIMessage": { - "message": "Вы вернулись к старому интерфейсу. Вы можете вернуться к новому с помощью опции в раскрывающемся меню в правом верхнем углу." + "message": "Вы вернулись к старой версии интерфейса пользователя. Вы можете переключиться на новую с помощью опции выпадающего меню в правом верхнем углу." }, "or": { "message": "или", "description": "choice between creating or importing a new account" }, "passwordCorrect": { - "message": "Убедитесь, что ваш пароль правильный." + "message": "Убедитесь, что ваш пароль верный." }, "passwordMismatch": { "message": "пароли не совпадают", @@ -528,27 +555,30 @@ "description": "in password creation process, the password is not long enough to be secure" }, "pastePrivateKey": { - "message": "Вставьте свою личную строку:", + "message": "Вставьте ваш закрытый ключ тут:", "description": "For importing an account from a private key" }, "pasteSeed": { - "message": "Вставьте здесь свою семенную фразу!" + "message": "Вставьте вашу ключевую фразу!" }, "personalAddressDetected": { - "message": "Персональный адрес обнаружен. Введите адрес контракта токена." + "message": "Обнаружен персональный адрес. Введите адрес контракта токена." }, "pleaseReviewTransaction": { "message": "Проверьте транзакцию." }, + "popularTokens": { + "message": "Популярные токены" + }, "privacyMsg": { - "message": "Политика Конфиденциальности" + "message": "Политика конфиденциальности" }, "privateKey": { "message": "Закрытый ключ", "description": "select this type of file to use to import an account" }, "privateKeyWarning": { - "message": "Предупреждение: никогда не раскрывайте этот ключ. Любой, у кого есть ваши личные ключи, может украсть любые активы, хранящиеся в вашем аккаунте." + "message": "Предупреждение: Никогда не раскрывайте этот ключ. Любой, у кого есть ваши закрытые ключи, может украсть любые активы, хранящиеся на счету." }, "privateNetwork": { "message": "Частная сеть" @@ -557,126 +587,165 @@ "message": "Показать QR-код" }, "readdToken": { - "message": "Вы можете добавить этот токен в будущем, перейдя в “Добавить токен” в меню параметров вашего аккаунта." + "message": "Вы можете в будущем добавить обратно этот токен, выбрав пункт меню “Добавить токен”." }, "readMore": { - "message": "Подробнее читайте здесь." + "message": "Узнать больше тут." }, "readMore2": { - "message": "Прочитайте больше." + "message": "Узнать больше." }, "receive": { "message": "Получить" }, "recipientAddress": { - "message": "Адрес Получателя" + "message": "Адрес получателя" }, "refundAddress": { - "message": "Ваш Адрес Возврата" + "message": "Ваш адрес для возврата средств" }, "rejected": { - "message": "Отклонено" + "message": "Отклонена" }, "resetAccount": { "message": "Сбросить аккаунт" }, "restoreFromSeed": { - "message": "Восстановить от семенной фразы" + "message": "Восстановить из ключевой фразы" + }, + "restoreVault": { + "message": "Восстановить кошелек" }, "required": { - "message": "Необходимо" + "message": "Обязательное поле" }, "retryWithMoreGas": { - "message": "Повторите попытку с более высокой ценой на газ здесь" + "message": "Повторите попытку с большей ценой за газRetry with a higher gas price here" + }, + "walletSeed": { + "message": "Ключевая фраза кошелька" }, "revealSeedWords": { - "message": "Раскрыть семенные слова" + "message": "Показать ключевую фразу" }, "revealSeedWordsWarning": { - "message": "Не восстанавливайте семенные слова в общественном месте! Эти слова могут использоваться для кражи всех ваших аккаунтах." + "message": "Не восстанавливайте ключевую фразу в общественном месте! Она может быть использована для кражи всех ваших счетов." }, "revert": { - "message": "Откат" + "message": "Восстановить" }, "rinkeby": { - "message": "Rinkeby тестовая сеть" + "message": "Тестовая сеть Rinkeby" }, "ropsten": { - "message": "Ropsten тестовая сеть" + "message": "Тестовая сеть Ropsten" + }, + "currentRpc": { + "message": "Current RPC" + }, + "connectingToMainnet": { + "message": "Соединение с основной сетью Ethereum" + }, + "connectingToRopsten": { + "message": "Соединение с тестовой сетью Ropsten" + }, + "connectingToKovan": { + "message": "Соединение с тестовой сетью Kovan" + }, + "connectingToRinkeby": { + "message": "Соединение с тестовой сетью Rinkeby" + }, + "connectingToUnknown": { + "message": "Соединение с неизвестной сетью" }, "sampleAccountName": { - "message": "Например, Мой новый аккаунт", + "message": "Например, Мой новый счет", "description": "Help user understand concept of adding a human-readable name to their account" }, "save": { "message": "Сохранить" }, "saveAsFile": { - "message": "Сохранить как Файл", + "message": "Сохранить в виде файла", "description": "Account export process" }, "saveSeedAsFile": { - "message": "Сохранить Семенные Слова Как Файл" + "message": "Сохранить ключевую фразу в виде файла" }, "search": { "message": "Поиск" }, "secretPhrase": { - "message": "Введите свою секретную двенадцатисловную фразу здесь, чтобы восстановить хранилище." + "message": "Введите вашу ключевую фразу из 12 слов, чтобы восстановить кошелек." + }, + "newPassword8Chars": { + "message": "Новый пароль (мин. 8 символов)" }, "seedPhraseReq": { - "message": "семенные фразы длиной 12 слов" + "message": "ключевые фразы имеют длину 12 слов" }, "select": { "message": "Выбрать" }, "selectCurrency": { - "message": "Выберите Валюту" + "message": "Выберите валюту" }, "selectService": { - "message": "Выберите Сервис" + "message": "Выберите сервис" }, "selectType": { - "message": "Выберите Тип" + "message": "Выберите тип" }, "send": { - "message": "Послать" + "message": "Отправить" }, "sendETH": { "message": "Отправить ETH" }, "sendTokens": { - "message": "Отправить Токены" + "message": "Отправить токены" + }, + "onlySendToEtherAddress": { + "message": "Отправляйте ETH только на Ethereum адреса." + }, + "searchTokens": { + "message": "Поиск токенов" }, "sendTokensAnywhere": { - "message": "Отправить Токены кому-либо с аккаунтом Ethereum" + "message": "Отправить токены любому, у кого есть счет Ethereum" }, "settings": { "message": "Настройки" }, + "info": { + "message": "Информация" + }, "shapeshiftBuy": { - "message": "Купить с помощью Shapeshift" + "message": "Купить через Shapeshift" }, "showPrivateKeys": { - "message": "Показать приватные ключи" + "message": "Показать закрытые ключи" }, "showQRCode": { "message": "Показать QR-код" }, "sign": { - "message": "Знак" + "message": "Подпись" + }, + "signed": { + "message": "Подписана" }, "signMessage": { - "message": "Нодписать сообщение" + "message": "Подписать сообщение" }, "signNotice": { - "message": "Подписание этого сообщения может иметь \nопасные побочные эффекты. Только подписывайте сообщения \nс сайтов, которым вы полностью доверяете своим аккаунтом. Этот опасный метод будет удален в будущей версии." + "message": "Подпись этого сообщения может иметь \nопасные побочные эффекты. Подписывайте только сообщения \nс сайтов, которым вы полностью доверяете свой аккаунт. Этот опасный метод будет удален в будущей версии." }, "sigRequest": { - "message": "Запрос на подпись" + "message": "Запрос подписи" }, "sigRequested": { - "message": "Подпись Запрошена" + "message": "Подпись запрошена" }, "spaceBetween": { "message": "между словами может быть только пробел" @@ -685,53 +754,59 @@ "message": "Статус" }, "stateLogs": { - "message": "Логи Статуса" + "message": "Журнал состояния" }, "stateLogsDescription": { - "message": "Логи статуса содержат ваши общедоступные адреса и отправленные транзакции." + "message": "Журнал состояния содержит ваши публичные адреса счетов и совершенные транзакции." + }, + "stateLogError": { + "message": "Ошибка при получении журнала состояния." }, "submit": { "message": "Отправить" }, + "submitted": { + "message": "Отправлена" + }, "supportCenter": { - "message": "Посетите наш Центр поддержки" + "message": "Перейти в наш Центр поддержки" }, "symbolBetweenZeroTen": { "message": "Символ должен быть от 0 до 10 символов." }, "takesTooLong": { - "message": "Занимает слишком долго?" + "message": "Слишком долго?" }, "terms": { - "message": "Условия Эксплуатации" + "message": "Условия пользования" }, "testFaucet": { - "message": "Тестовый Кран" + "message": "Тестовый кран" }, "to": { - "message": "К" + "message": "Получатель: " }, "toETHviaShapeShift": { "message": "$1 в ETH через ShapeShift", "description": "system will fill in deposit type in start of message" }, "tokenAddress": { - "message": "Адрес Токена" + "message": "Адрес токена" }, "tokenAlreadyAdded": { - "message": "Токен уже добавлен." + "message": "Токен уже был добавлен." }, "tokenBalance": { - "message": "Баланс Вашых Tокенов:" + "message": "Баланс ваших токенов:" }, "tokenSelection": { - "message": "Поиск токенов или выбор из нашего списка популярных токенов." + "message": "Поищите токен или выберите из нашего списка популярных токенов." }, "tokenSymbol": { - "message": "Символ Токена" + "message": "Символ токена" }, "tokenWarning1": { - "message": "Следите за токенами, которые вы купили с помощью аккаунта MetaMask. Если вы купили токены, используя другой аккаунт, эти токены здесь не появятся." + "message": "Отслеживаются токены, купленные на счет в MetaMask. Если вы купили токены, используя другой счет, такие токены не будут тут отображены." }, "total": { "message": "Всего" @@ -740,35 +815,38 @@ "message": "транзакции" }, "transactionMemo": { - "message": "Транзакционная записка (необязательно)" + "message": "Транзакционные данные (необязательный)" }, "transactionNumber": { - "message": "Номер Транзакции" + "message": "Номер транзакции" }, "transfers": { "message": "Переводы" }, "troubleTokenBalances": { - "message": "У нас были проблемы с загрузкой ваших токенов. Вы можете просмотреть их ", + "message": "Возникли проблемы при загрузке балансов токенов. Вы можете посмотреть их ", "description": "Followed by a link (here) to view token balances" }, "twelveWords": { - "message": "Эти 12 слов - единственный способ восстановить ваши учетные записи MetaMask.\nСохраните их где-нибудь в безопасности и в тайне." + "message": "Эти 12 слов являются единственной возможностью восстановить ваши счета в MetaMask.\nСохраните из в надежном секретном месте." }, "typePassword": { - "message": "Введите Пароль" + "message": "Введите пароль" }, "uiWelcome": { - "message": "Добро пожаловать в новый интерфейс (бета-версия)" + "message": "Новый интерфейс (Beta)" }, "uiWelcomeMessage": { - "message": "Теперь вы используете новый интерфейс Metamask. Осмотритесь, попробуйте новые функции, такие как отправку токенов, и сообщите нам, есть ли у вас какие-либо проблемы." + "message": "Теперь вы используете новый интерфейс пользователя MetaMask. Осмотритесь, попробуйте новые функции, например, отправить токены и, если возникнут проблемы, сообщите нам." + }, + "unapproved": { + "message": "Не одобрена" }, "unavailable": { - "message": "Недоступен" + "message": "Недоступный" }, "unknown": { - "message": "Неизвестный" + "message": "Неизвестно" }, "unknownNetwork": { "message": "Неизвестная частная сеть" @@ -777,7 +855,7 @@ "message": "Неизвестный идентификатор сети" }, "uriErrorMsg": { - "message": "Для URI требуется соответствующий префикс HTTP / HTTPS." + "message": "Для URI требуется соответствующий префикс HTTP/HTTPS." }, "usaOnly": { "message": "Только США", @@ -787,19 +865,19 @@ "message": "Используется различными клиентами" }, "useOldUI": { - "message": "Использовать старый интерфейс" + "message": "Использовать старый интерфейс пользователя" }, "validFileImport": { - "message": "Вы должны выбрать действительный файл для импорта." + "message": "Вам нужно выбрать правильный файл для импорта." }, "vaultCreated": { - "message": "Создано хранилище" + "message": "Кошелек был создан" }, "viewAccount": { - "message": "Посмотреть аккаунт" + "message": "Посмотреть счет" }, "visitWebSite": { - "message": "Посетите наш сайт" + "message": "Перейти на наш сайт" }, "warning": { "message": "Предупреждение" @@ -811,7 +889,7 @@ "message": "Что это?" }, "yourSigRequested": { - "message": "Ваша подпись запрашивается" + "message": "Запрашивается ваша подпись" }, "youSign": { "message": "Вы подписываете" diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json index 0532f11b2..b089f3476 100644 --- a/app/_locales/sl/messages.json +++ b/app/_locales/sl/messages.json @@ -223,7 +223,7 @@ "done": { "message": "Končano" }, - "downloadStatelogs": { + "downloadStateLogs": { "message": "Prenesi state dnevnike" }, "edit": { diff --git a/app/_locales/th/messages.json b/app/_locales/th/messages.json index 887714f3f..3d7dec226 100644 --- a/app/_locales/th/messages.json +++ b/app/_locales/th/messages.json @@ -223,7 +223,7 @@ "done": { "message": "เสร็จสิ้น" }, - "downloadStatelogs": { + "downloadStateLogs": { "message": "ดาวน์โหลดล็อกสถานะ" }, "edit": { diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json index 90f63c6a6..9aaee0e16 100644 --- a/app/_locales/zh_TW/messages.json +++ b/app/_locales/zh_TW/messages.json @@ -171,6 +171,9 @@ "customGas": { "message": "自訂 Gas" }, + "customToken": { + "message": "自訂代幣" + }, "customize": { "message": "自訂" }, @@ -184,7 +187,7 @@ "message": "小數點精度" }, "defaultNetwork": { - "message": "預設Ether交易網路為主網(Main Net)。" + "message": "預設 Ether 交易網路為主網路(Main Net)。" }, "denExplainer": { "message": "你的 DEN 是在你的 MetaMask 中的加密密碼儲存庫。" @@ -215,7 +218,7 @@ "message": "從 ShapeShift 存入" }, "depositShapeShiftExplainer": { - "message": "如果你擁有其他加密貨幣,你可以直接交易並存入 Ether 到你的MetaMask錢包。不需要開帳戶。" + "message": "如果你擁有其他加密貨幣,你可以直接交易並存入 Ether 到你的 MetaMask 錢包。不需要開帳戶。" }, "details": { "message": "詳情" @@ -227,12 +230,12 @@ "message": "直接存入 Ether" }, "directDepositEtherExplainer": { - "message": "如果你已經擁有了一些Ether,使用直接存入功能是讓你的新錢包最快取得Ether的方式。" + "message": "如果你已經擁有了一些 Ether,使用直接存入功能是讓你的新錢包最快取得 Ether 的方式。" }, "done": { "message": "完成" }, - "downloadStatelogs": { + "downloadStateLogs": { "message": "下載狀態紀錄" }, "dropped": { @@ -285,6 +288,9 @@ "message": "檔案導入失敗?點擊這裡!", "description": "Helps user import their account from a JSON file" }, + "followTwitter": { + "message": "追蹤 Twitter" + }, "from": { "message": "來源地址" }, @@ -313,6 +319,9 @@ "gasLimitTooLow": { "message": "Gas 上限至少為 21000" }, + "generatingSeed": { + "message": "產生助憶詞中..." + }, "gasPrice": { "message": "Gas 價格 (GWEI)" }, @@ -362,6 +371,9 @@ "importAccount": { "message": "導入帳戶" }, + "importAccountMsg": { + "message":" 匯入的帳戶與您原有 MetaMask 帳戶的助憶詞並無關聯. 請查看與導入帳戶相關的資料 " + }, "importAnAccount": { "message": "導入一個帳戶" }, @@ -400,12 +412,15 @@ "message": "無效的 RPC URI" }, "jsonFail": { - "message": "有東西出錯了. 請確認你的 JSON 檔案格式正確." + "message": "有東西出錯了. 請確認你的 JSON 檔案格式正確。" }, "jsonFile": { "message": "JSON 檔案", "description": "format for importing an account" }, + "keepTrackTokens": { + "message": "持續追蹤您 MetaMask 帳戶中的代幣。" + }, "kovan": { "message": "Kovan 測試網路" }, @@ -415,6 +430,9 @@ "max": { "message": "最大值" }, + "learnMore": { + "message": "了解更多。" + }, "lessThanMax": { "message": "必須小於等於 $1.", "description": "helper for inputting hex as decimal input" @@ -437,17 +455,20 @@ "localhost": { "message": "Localhost 8545" }, + "login": { + "message": "登入" + }, "logout": { "message": "登出" }, "loose": { - "message": "非Metamask帳號" + "message": "非 MetaMask 帳號" }, "loweCaseWords": { "message": "助憶詞僅包含小寫字元" }, "mainnet": { - "message": "主乙太坊網路" + "message": "乙太坊 主網路" }, "message": { "message": "訊息" @@ -465,7 +486,7 @@ "message": "必須選擇至少 1 代幣." }, "needEtherInWallet": { - "message": "要使用 MetaMask 存取 DAPP時,您的錢包中需要有 Ether。" + "message": "要使用 MetaMask 存取 DAPP 時,您的錢包中需要有 Ether。" }, "needImportFile": { "message": "您必須選擇一個檔案來導入。", @@ -475,6 +496,9 @@ "message": "您必須為選擇好的檔案輸入密碼。", "description": "Password and file needed to import an account" }, + "negativeETH": { + "message": "不能送出負值的 ETH。" + }, "networks": { "message": "網路" }, @@ -525,6 +549,9 @@ "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" @@ -546,6 +573,12 @@ "pleaseReviewTransaction": { "message": "請檢查你的交易。" }, + "popularTokens": { + "message": "常見的代幣" + }, + "privacyMsg": { + "message": "隱私政策" + }, "privateKey": { "message": "私鑰", "description": "select this type of file to use to import an account" @@ -681,6 +714,9 @@ "onlySendToEtherAddress": { "message": "只發送 ETH 到乙太坊地址." }, + "searchTokens": { + "message": "搜尋代幣" + }, "sendTokensAnywhere": { "message": "發送代幣給擁有乙太坊帳戶的任何人" }, @@ -700,13 +736,16 @@ "message": "顯示 QR Code" }, "sign": { - "message": "簽名" + "message": "簽署" + }, + "signed": { + "message": "已簽署" }, "signMessage": { "message": "簽署訊息" }, "signNotice": { - "message": "簽署此訊息可能會產生危險的副作用。 \n只從你完全信任的網站上簽名。這種危險的方法;將在未來的版本中被移除。" + "message": "簽署此訊息可能會產生危險地副作用。 \n只從你完全信任的網站上簽署。這種危險的方法;將在未來的版本中被移除。" }, "sigRequest": { "message": "請求簽署" @@ -767,7 +806,7 @@ "message": "代幣餘額:" }, "tokenSelection": { - "message": "搜尋代幣或是從熱門代幣列表中選擇。" + "message": "搜尋代幣或是從常見代幣列表中選擇。" }, "tokenSymbol": { "message": "代幣代號" @@ -804,7 +843,7 @@ "message": "歡迎使用新版界面 (Beta)" }, "uiWelcomeMessage": { - "message": "你現在正在使用新的 Metamask 界面。試試諸如發送代幣等新功能,有任何問題請告知我們。" + "message": "你現在正在使用新版 MetaMask 界面。試試諸如發送代幣等新功能吧,有任何問題請告知我們。" }, "unapproved": { "message": "未同意" diff --git a/app/home.html b/app/home.html index cfb4b00a0..4fad0f993 100644 --- a/app/home.html +++ b/app/home.html @@ -3,10 +3,10 @@ <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no"> - <title>MetaMask Plugin</title> + <title>MetaMask</title> </head> <body> <div id="app-content"></div> - <script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script> + <script src="./ui.js" type="text/javascript" charset="utf-8"></script> </body> </html> diff --git a/app/manifest.json b/app/manifest.json index a20f9b976..dc46f1ca4 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "4.4.0", + "version": "4.5.5", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", @@ -27,8 +27,8 @@ "default_locale": "en", "background": { "scripts": [ - "scripts/chromereload.js", - "scripts/background.js" + "chromereload.js", + "background.js" ], "persistent": true }, @@ -48,7 +48,7 @@ "https://*/*" ], "js": [ - "scripts/contentscript.js" + "contentscript.js" ], "run_at": "document_start", "all_frames": true @@ -62,7 +62,7 @@ "https://*.infura.io/" ], "web_accessible_resources": [ - "scripts/inpage.js" + "inpage.js" ], "externally_connectable": { "matches": [ diff --git a/app/notification.html b/app/notification.html index f10cbbf41..457ba7137 100644 --- a/app/notification.html +++ b/app/notification.html @@ -11,6 +11,6 @@ </head> <body class="notification" style="height:600px;"> <div id="app-content"></div> - <script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script> + <script src="./ui.js" type="text/javascript" charset="utf-8"></script> </body> </html> diff --git a/app/popup.html b/app/popup.html index bf09b97ca..3acfd8c55 100644 --- a/app/popup.html +++ b/app/popup.html @@ -3,10 +3,10 @@ <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no"> - <title>MetaMask Plugin</title> + <title>MetaMask</title> </head> <body style="width:357px; height:600px;"> <div id="app-content"></div> - <script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script> + <script src="./ui.js" type="text/javascript" charset="utf-8"></script> </body> </html> diff --git a/app/scripts/background.js b/app/scripts/background.js index 7bbaa89d6..ec586f642 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -19,10 +19,11 @@ const setupRaven = require('./lib/setupRaven') const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry') const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics') const EdgeEncryptor = require('./edge-encryptor') - +const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code') +const getObjStructure = require('./lib/getObjStructure') const STORAGE_KEY = 'metamask-config' -const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' +const METAMASK_DEBUG = process.env.METAMASK_DEBUG window.log = log log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn') @@ -58,7 +59,8 @@ setupMetamaskMeshMetrics() async function initialize () { const initState = await loadStateFromPersistence() - await setupController(initState) + const initLangCode = await getFirstPreferredLangCode() + await setupController(initState, initLangCode) log.debug('MetaMask initialization complete.') } @@ -76,6 +78,16 @@ async function loadStateFromPersistence () { diskStore.getState() || migrator.generateInitialState(firstTimeState) + // report migration errors to sentry + migrator.on('error', (err) => { + // get vault structure without secrets + const vaultStructure = getObjStructure(versionedData) + raven.captureException(err, { + // "extra" key is required by Sentry + extra: { vaultStructure }, + }) + }) + // migrate data versionedData = await migrator.migrateData(versionedData) if (!versionedData) { @@ -83,13 +95,20 @@ async function loadStateFromPersistence () { } // write to disk - if (localStore.isSupported) localStore.set(versionedData) + if (localStore.isSupported) { + localStore.set(versionedData) + } else { + // throw in setTimeout so as to not block boot + setTimeout(() => { + throw new Error('MetaMask - Localstore not supported') + }) + } // return just the data return versionedData.data } -function setupController (initState) { +function setupController (initState, initLangCode) { // // MetaMask Controller // @@ -101,6 +120,8 @@ function setupController (initState) { showUnapprovedTx: triggerUi, // initial state initState, + // initial locale code + initLangCode, // platform specific api platform, encryptor: isEdge ? new EdgeEncryptor() : undefined, diff --git a/app/scripts/config.js b/app/scripts/config.js index 74c5b576e..a8470ed82 100644 --- a/app/scripts/config.js +++ b/app/scripts/config.js @@ -13,7 +13,7 @@ const DEFAULT_RPC = 'rinkeby' const OLD_UI_NETWORK_TYPE = 'network' const BETA_UI_NETWORK_TYPE = 'networkBeta' -global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' +global.METAMASK_DEBUG = process.env.METAMASK_DEBUG module.exports = { network: { diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 7abbc60e7..fe1766273 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -7,8 +7,8 @@ const ObjectMultiplex = require('obj-multiplex') const extension = require('extensionizer') const PortStream = require('./lib/port-stream.js') -const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'scripts', 'inpage.js')).toString() -const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('scripts/inpage.js') + '\n' +const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString() +const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n' const inpageBundle = inpageContent + inpageSuffix // Eventually this streaming injection could be replaced with: @@ -96,7 +96,7 @@ function logStreamDisconnectWarning (remoteLabel, err) { } function shouldInjectWeb3 () { - return doctypeCheck() && suffixCheck() + return doctypeCheck() && suffixCheck() && documentElementCheck() && !blacklistedDomainCheck() } @@ -131,7 +131,11 @@ function documentElementCheck () { } function blacklistedDomainCheck () { - var blacklistedDomains = ['uscourts.gov', 'dropbox.com'] + var blacklistedDomains = [ + 'uscourts.gov', + 'dropbox.com', + 'webbyawards.com', + ] var currentUrl = window.location.href var currentRegex for (let i = 0; i < blacklistedDomains.length; i++) { diff --git a/app/scripts/controllers/blacklist.js b/app/scripts/controllers/blacklist.js index 33c31dab9..df41c90c0 100644 --- a/app/scripts/controllers/blacklist.js +++ b/app/scripts/controllers/blacklist.js @@ -41,9 +41,9 @@ class BlacklistController { scheduleUpdates () { if (this._phishingUpdateIntervalRef) return - this.updatePhishingList() + this.updatePhishingList().catch(log.warn) this._phishingUpdateIntervalRef = setInterval(() => { - this.updatePhishingList() + this.updatePhishingList().catch(log.warn) }, POLLING_INTERVAL) } @@ -57,4 +57,3 @@ class BlacklistController { } module.exports = BlacklistController - diff --git a/app/scripts/controllers/currency.js b/app/scripts/controllers/currency.js index 25a7a942e..36b8808aa 100644 --- a/app/scripts/controllers/currency.js +++ b/app/scripts/controllers/currency.js @@ -43,20 +43,19 @@ class CurrencyController { this.store.updateState({ conversionDate }) } - updateConversionRate () { - const currentCurrency = this.getCurrentCurrency() - return fetch(`https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`) - .then(response => response.json()) - .then((parsedResponse) => { + async updateConversionRate () { + let currentCurrency + try { + currentCurrency = this.getCurrentCurrency() + const response = await fetch(`https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`) + const parsedResponse = await response.json() this.setConversionRate(Number(parsedResponse.bid)) this.setConversionDate(Number(parsedResponse.timestamp)) - }).catch((err) => { - if (err) { - console.warn('MetaMask - Failed to query currency conversion.') - this.setConversionRate(0) - this.setConversionDate('N/A') - } - }) + } catch (err) { + log.warn(`MetaMask - Failed to query currency conversion:`, currentCurrency, err) + this.setConversionRate(0) + this.setConversionDate('N/A') + } } scheduleConversionInterval () { diff --git a/app/scripts/controllers/infura.js b/app/scripts/controllers/infura.js index 10adb1004..c6b4c9de2 100644 --- a/app/scripts/controllers/infura.js +++ b/app/scripts/controllers/infura.js @@ -19,15 +19,13 @@ class InfuraController { // Responsible for retrieving the status of Infura's nodes. Can return either // ok, degraded, or down. - checkInfuraNetworkStatus () { - return fetch('https://api.infura.io/v1/status/metamask') - .then(response => response.json()) - .then((parsedResponse) => { - this.store.updateState({ - infuraNetworkStatus: parsedResponse, - }) - return parsedResponse - }) + async checkInfuraNetworkStatus () { + const response = await fetch('https://api.infura.io/v1/status/metamask') + const parsedResponse = await response.json() + this.store.updateState({ + infuraNetworkStatus: parsedResponse, + }) + return parsedResponse } scheduleInfuraNetworkCheck () { @@ -35,7 +33,7 @@ class InfuraController { clearInterval(this.conversionInterval) } this.conversionInterval = setInterval(() => { - this.checkInfuraNetworkStatus() + this.checkInfuraNetworkStatus().catch(log.warn) }, POLLING_INTERVAL) } } diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 39d15fd83..b4819d951 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -11,6 +11,7 @@ class PreferencesController { tokens: [], useBlockie: false, featureFlags: {}, + currentLocale: opts.initLangCode, }, opts.initState) this.store = new ObservableStore(initState) } @@ -24,6 +25,10 @@ class PreferencesController { return this.store.getState().useBlockie } + setCurrentLocale (key) { + this.store.updateState({ currentLocale: key }) + } + setSelectedAddress (_address) { return new Promise((resolve, reject) => { const address = normalizeAddress(_address) diff --git a/app/scripts/controllers/shapeshift.js b/app/scripts/controllers/shapeshift.js index 3d955c01f..3bbfaa1c5 100644 --- a/app/scripts/controllers/shapeshift.js +++ b/app/scripts/controllers/shapeshift.js @@ -45,18 +45,19 @@ class ShapeshiftController { }) } - updateTx (tx) { - const url = `https://shapeshift.io/txStat/${tx.depositAddress}` - return fetch(url) - .then((response) => { - return response.json() - }).then((json) => { + async updateTx (tx) { + try { + const url = `https://shapeshift.io/txStat/${tx.depositAddress}` + const response = await fetch(url) + const json = await response.json() tx.response = json if (tx.response.status === 'complete') { tx.time = new Date().getTime() } return tx - }) + } catch (err) { + log.warn(err) + } } saveTx (tx) { diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 3e3909361..336b0d8f7 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -161,9 +161,11 @@ module.exports = class TransactionController extends EventEmitter { this.emit(`${txMeta.id}:unapproved`, txMeta) } - async newUnapprovedTransaction (txParams) { + async newUnapprovedTransaction (txParams, opts = {}) { log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) const initialTxMeta = await this.addUnapprovedTransaction(txParams) + initialTxMeta.origin = opts.origin + this.txStateManager.updateTx(initialTxMeta, '#newUnapprovedTransaction - adding the origin') // listen for tx completion (success, fail) return new Promise((resolve, reject) => { this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => { @@ -183,14 +185,15 @@ module.exports = class TransactionController extends EventEmitter { async addUnapprovedTransaction (txParams) { // validate - await this.txGasUtil.validateTxParams(txParams) + const normalizedTxParams = this._normalizeTxParams(txParams) + this._validateTxParams(normalizedTxParams) // construct txMeta - const txMeta = this.txStateManager.generateTxMeta({txParams}) + let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams }) this.addTx(txMeta) this.emit('newUnapprovedTx', txMeta) // add default tx params try { - await this.addTxDefaults(txMeta) + txMeta = await this.addTxDefaults(txMeta) } catch (error) { console.log(error) this.txStateManager.setTxStatusFailed(txMeta.id, error) @@ -250,7 +253,7 @@ module.exports = class TransactionController extends EventEmitter { // wait for a nonce nonceLock = await this.nonceTracker.getNonceLock(fromAddress) // add nonce to txParams - // if txMeta has lastGasPrice then it is a retry at same nonce with higher + // 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)) @@ -273,12 +276,14 @@ module.exports = class TransactionController extends EventEmitter { async signTransaction (txId) { const txMeta = this.txStateManager.getTx(txId) - const txParams = txMeta.txParams - const fromAddress = txParams.from // add network/chain id - txParams.chainId = ethUtil.addHexPrefix(this.getChainId().toString(16)) + const chainId = this.getChainId() + const txParams = Object.assign({}, txMeta.txParams, { chainId }) + // sign tx + const fromAddress = txParams.from const ethTx = new Transaction(txParams) await this.signEthTx(ethTx, fromAddress) + // set state to signed this.txStateManager.setTxStatusSigned(txMeta.id) const rawTx = ethUtil.bufferToHex(ethTx.serialize()) return rawTx @@ -309,6 +314,60 @@ module.exports = class TransactionController extends EventEmitter { // PRIVATE METHODS // + _normalizeTxParams (txParams) { + // functions that handle normalizing of that key in txParams + const whiteList = { + from: from => ethUtil.addHexPrefix(from).toLowerCase(), + to: to => ethUtil.addHexPrefix(txParams.to).toLowerCase(), + nonce: nonce => ethUtil.addHexPrefix(nonce), + value: value => ethUtil.addHexPrefix(value), + data: data => ethUtil.addHexPrefix(data), + gas: gas => ethUtil.addHexPrefix(gas), + gasPrice: gasPrice => ethUtil.addHexPrefix(gasPrice), + } + + // apply only keys in the whiteList + const normalizedTxParams = {} + Object.keys(whiteList).forEach((key) => { + if (txParams[key]) normalizedTxParams[key] = whiteList[key](txParams[key]) + }) + + return normalizedTxParams + } + + _validateTxParams (txParams) { + this._validateFrom(txParams) + this._validateRecipient(txParams) + if ('value' in txParams) { + const value = txParams.value.toString() + if (value.includes('-')) { + throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`) + } + + if (value.includes('.')) { + throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`) + } + } + } + + _validateFrom (txParams) { + if ( !(typeof txParams.from === 'string') ) throw new Error(`Invalid from address ${txParams.from} not a string`) + if (!ethUtil.isValidAddress(txParams.from)) throw new Error('Invalid from address') + } + + _validateRecipient (txParams) { + if (txParams.to === '0x' || txParams.to === null ) { + if (txParams.data) { + delete txParams.to + } else { + throw new Error('Invalid recipient address') + } + } else if ( txParams.to !== undefined && !ethUtil.isValidAddress(txParams.to) ) { + throw new Error('Invalid recipient address') + } + return txParams + } + _markNonceDuplicatesDropped (txId) { this.txStateManager.setTxStatusConfirmed(txId) // get the confirmed transactions nonce and from address diff --git a/app/scripts/first-time-state.js b/app/scripts/first-time-state.js index 5e8577100..3063df627 100644 --- a/app/scripts/first-time-state.js +++ b/app/scripts/first-time-state.js @@ -1,6 +1,6 @@ // test and development environment variables const env = process.env.METAMASK_ENV -const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' +const METAMASK_DEBUG = process.env.METAMASK_DEBUG // // The default state of MetaMask diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 9261e7d64..ec99bfc35 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -9,7 +9,7 @@ const setupDappAutoReload = require('./lib/auto-reload.js') const MetamaskInpageProvider = require('./lib/inpage-provider.js') restoreContextAfterImports() -const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' +const METAMASK_DEBUG = process.env.METAMASK_DEBUG window.log = log log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn') diff --git a/app/scripts/lib/extractEthjsErrorMessage.js b/app/scripts/lib/extractEthjsErrorMessage.js new file mode 100644 index 000000000..bac541735 --- /dev/null +++ b/app/scripts/lib/extractEthjsErrorMessage.js @@ -0,0 +1,27 @@ +const ethJsRpcSlug = 'Error: [ethjs-rpc] rpc error with payload ' +const errorLabelPrefix = 'Error: ' + +module.exports = extractEthjsErrorMessage + + +// +// ethjs-rpc provides overly verbose error messages +// if we detect this type of message, we extract the important part +// Below is an example input and output +// +// Error: [ethjs-rpc] rpc error with payload {"id":3947817945380,"jsonrpc":"2.0","params":["0xf8eb8208708477359400830398539406012c8cf97bead5deae237070f9587f8e7a266d80b8843d7d3f5a0000000000000000000000000000000000000000000000000000000000081d1a000000000000000000000000000000000000000000000000001ff973cafa800000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000003f48025a04c32a9b630e0d9e7ff361562d850c86b7a884908135956a7e4a336fa0300d19ca06830776423f25218e8d19b267161db526e66895567147015b1f3fc47aef9a3c7"],"method":"eth_sendRawTransaction"} Error: replacement transaction underpriced +// +// Transaction Failed: replacement transaction underpriced +// + + +function extractEthjsErrorMessage(errorMessage) { + const isEthjsRpcError = errorMessage.includes(ethJsRpcSlug) + if (isEthjsRpcError) { + const payloadAndError = errorMessage.slice(ethJsRpcSlug.length) + const originalError = payloadAndError.slice(payloadAndError.indexOf(errorLabelPrefix) + errorLabelPrefix.length) + return originalError + } else { + return errorMessage + } +} diff --git a/app/scripts/lib/get-first-preferred-lang-code.js b/app/scripts/lib/get-first-preferred-lang-code.js new file mode 100644 index 000000000..e3635434e --- /dev/null +++ b/app/scripts/lib/get-first-preferred-lang-code.js @@ -0,0 +1,18 @@ +const extension = require('extensionizer') +const promisify = require('pify') +const allLocales = require('../../_locales/index.json') + +const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().replace('_', '-')) + +async function getFirstPreferredLangCode () { + const userPreferredLocaleCodes = await promisify( + extension.i18n.getAcceptLanguages, + { errorFirst: false } + )() + const firstPreferredLangCode = userPreferredLocaleCodes + .map(code => code.toLowerCase()) + .find(code => existingLocaleCodes.includes(code)) + return firstPreferredLangCode || 'en' +} + +module.exports = getFirstPreferredLangCode diff --git a/app/scripts/lib/getObjStructure.js b/app/scripts/lib/getObjStructure.js new file mode 100644 index 000000000..3db389507 --- /dev/null +++ b/app/scripts/lib/getObjStructure.js @@ -0,0 +1,33 @@ +const clone = require('clone') + +module.exports = getObjStructure + +// This will create an object that represents the structure of the given object +// it replaces all values with the result of their type + +// { +// "data": { +// "CurrencyController": { +// "conversionDate": "number", +// "conversionRate": "number", +// "currentCurrency": "string" +// } +// } + +function getObjStructure(obj) { + const structure = clone(obj) + return deepMap(structure, (value) => { + return value === null ? 'null' : typeof value + }) +} + +function deepMap(target = {}, visit) { + Object.entries(target).forEach(([key, value]) => { + if (typeof value === 'object' && value !== null) { + target[key] = deepMap(value, visit) + } else { + target[key] = visit(value) + } + }) + return target +} diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index 4fd2cae92..85c2717ea 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -1,6 +1,9 @@ -class Migrator { +const EventEmitter = require('events') + +class Migrator extends EventEmitter { constructor (opts = {}) { + super() const migrations = opts.migrations || [] // sort migrations by version this.migrations = migrations.sort((a, b) => a.version - b.version) @@ -12,13 +15,29 @@ class Migrator { // run all pending migrations on meta in place async migrateData (versionedData = this.generateInitialState()) { + // get all migrations that have not yet been run const pendingMigrations = this.migrations.filter(migrationIsPending) + // perform each migration for (const index in pendingMigrations) { const migration = pendingMigrations[index] - versionedData = await migration.migrate(versionedData) - if (!versionedData.data) throw new Error('Migrator - migration returned empty data') - if (versionedData.version !== undefined && versionedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly') + try { + // attempt migration and validate + const migratedData = await migration.migrate(versionedData) + if (!migratedData.data) throw new Error('Migrator - migration returned empty data') + if (migratedData.version !== undefined && migratedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly') + // accept the migration as good + versionedData = migratedData + } catch (err) { + // rewrite error message to add context without clobbering stack + const originalErrorMessage = err.message + err.message = `MetaMask Migration Error #${migration.version}: ${originalErrorMessage}` + console.warn(err.stack) + // emit error instead of throw so as to not break the run (gracefully fail) + this.emit('error', err) + // stop migrating and use state as is + return versionedData + } } return versionedData diff --git a/app/scripts/lib/nonce-tracker.js b/app/scripts/lib/nonce-tracker.js index ed9dd3f11..5b1cd7f43 100644 --- a/app/scripts/lib/nonce-tracker.js +++ b/app/scripts/lib/nonce-tracker.js @@ -31,14 +31,13 @@ class NonceTracker { const networkNonceResult = await this._getNetworkNextNonce(address) const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address) const nextNetworkNonce = networkNonceResult.nonce - const highestLocalNonce = highestLocallyConfirmed - const highestSuggested = Math.max(nextNetworkNonce, highestLocalNonce) + const highestSuggested = Math.max(nextNetworkNonce, highestLocallyConfirmed) const pendingTxs = this.getPendingTransactions(address) const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0 nonceDetails.params = { - highestLocalNonce, + highestLocallyConfirmed, highestSuggested, nextNetworkNonce, } diff --git a/app/scripts/lib/reportFailedTxToSentry.js b/app/scripts/lib/reportFailedTxToSentry.js index ee73f6845..e09f4f1f8 100644 --- a/app/scripts/lib/reportFailedTxToSentry.js +++ b/app/scripts/lib/reportFailedTxToSentry.js @@ -1,5 +1,4 @@ -const ethJsRpcSlug = 'Error: [ethjs-rpc] rpc error with payload ' -const errorLabelPrefix = 'Error: ' +const extractEthjsErrorMessage = require('./extractEthjsErrorMessage') module.exports = reportFailedTxToSentry @@ -9,30 +8,9 @@ module.exports = reportFailedTxToSentry // function reportFailedTxToSentry({ raven, txMeta }) { - const errorMessage = extractErrorMessage(txMeta.err.message) + const errorMessage = 'Transaction Failed: ' + extractEthjsErrorMessage(txMeta.err.message) raven.captureMessage(errorMessage, { // "extra" key is required by Sentry extra: txMeta, }) } - -// -// ethjs-rpc provides overly verbose error messages -// if we detect this type of message, we extract the important part -// Below is an example input and output -// -// Error: [ethjs-rpc] rpc error with payload {"id":3947817945380,"jsonrpc":"2.0","params":["0xf8eb8208708477359400830398539406012c8cf97bead5deae237070f9587f8e7a266d80b8843d7d3f5a0000000000000000000000000000000000000000000000000000000000081d1a000000000000000000000000000000000000000000000000001ff973cafa800000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000003f48025a04c32a9b630e0d9e7ff361562d850c86b7a884908135956a7e4a336fa0300d19ca06830776423f25218e8d19b267161db526e66895567147015b1f3fc47aef9a3c7"],"method":"eth_sendRawTransaction"} Error: replacement transaction underpriced -// -// Transaction Failed: replacement transaction underpriced -// - -function extractErrorMessage(errorMessage) { - const isEthjsRpcError = errorMessage.includes(ethJsRpcSlug) - if (isEthjsRpcError) { - const payloadAndError = errorMessage.slice(ethJsRpcSlug.length) - const originalError = payloadAndError.slice(payloadAndError.indexOf(errorLabelPrefix) + errorLabelPrefix.length) - return `Transaction Failed: ${originalError}` - } else { - return `Transaction Failed: ${errorMessage}` - } -} diff --git a/app/scripts/lib/setupRaven.js b/app/scripts/lib/setupRaven.js index 02c01b755..9ec9a256f 100644 --- a/app/scripts/lib/setupRaven.js +++ b/app/scripts/lib/setupRaven.js @@ -1,5 +1,6 @@ const Raven = require('raven-js') -const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' +const METAMASK_DEBUG = process.env.METAMASK_DEBUG +const extractEthjsErrorMessage = require('./extractEthjsErrorMessage') const PROD = 'https://3567c198f8a8412082d32655da2961d0@sentry.io/273505' const DEV = 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496' @@ -21,8 +22,22 @@ function setupRaven(opts) { const client = Raven.config(ravenTarget, { release, transport: function(opts) { - // modify report urls const report = opts.data + // simplify certain complex error messages + report.exception.values.forEach(item => { + let errorMessage = item.value + // simplify ethjs error messages + errorMessage = extractEthjsErrorMessage(errorMessage) + // simplify 'Transaction Failed: known transaction' + if (errorMessage.indexOf('Transaction Failed: known transaction') === 0) { + // cut the hash from the error message + errorMessage = 'Transaction Failed: known transaction' + } + // finalize + item.value = errorMessage + }) + + // modify report urls rewriteReportUrls(report) // make request normally client._makeRequest(opts) diff --git a/app/scripts/lib/tx-gas-utils.js b/app/scripts/lib/tx-gas-utils.js index 0fa9dd8d4..c579e462a 100644 --- a/app/scripts/lib/tx-gas-utils.js +++ b/app/scripts/lib/tx-gas-utils.js @@ -4,7 +4,7 @@ const { BnMultiplyByFraction, bnToHex, } = require('./util') -const { addHexPrefix, isValidAddress } = require('ethereumjs-util') +const { addHexPrefix } = require('ethereumjs-util') const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send. /* @@ -52,7 +52,9 @@ module.exports = class TxGasUtil { // if recipient has no code, gas is 21k max: const recipient = txParams.to const hasRecipient = Boolean(recipient) - const code = await this.query.getCode(recipient) + let code + if (recipient) code = await this.query.getCode(recipient) + if (hasRecipient && (!code || code === '0x')) { txParams.gas = SIMPLE_GAS_COST txMeta.simpleSend = true // Prevents buffer addition @@ -98,30 +100,4 @@ module.exports = class TxGasUtil { // otherwise use blockGasLimit return bnToHex(upperGasLimitBn) } - - async validateTxParams (txParams) { - this.validateRecipient(txParams) - if ('value' in txParams) { - const value = txParams.value.toString() - if (value.includes('-')) { - throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`) - } - - if (value.includes('.')) { - throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`) - } - } - } - validateRecipient (txParams) { - if (txParams.to === '0x' || txParams.to === null ) { - if (txParams.data) { - delete txParams.to - } else { - throw new Error('Invalid recipient address') - } - } else if ( txParams.to !== undefined && !isValidAddress(txParams.to) ) { - throw new Error('Invalid recipient address') - } - return txParams - } -} +}
\ No newline at end of file diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js index ad07c813f..d8ea17400 100644 --- a/app/scripts/lib/tx-state-manager.js +++ b/app/scripts/lib/tx-state-manager.js @@ -38,11 +38,6 @@ module.exports = class TransactionStateManager extends EventEmitter { }, opts) } - // Returns the number of txs for the current network. - getTxCount () { - return this.getTxList().length - } - getTxList () { const network = this.getNetwork() const fullTxList = this.getFullTxList() @@ -88,7 +83,7 @@ module.exports = class TransactionStateManager extends EventEmitter { txMeta.history.push(snapshot) const transactions = this.getFullTxList() - const txCount = this.getTxCount() + const txCount = transactions.length const txHistoryLimit = this.txHistoryLimit // checks if the length of the tx history is @@ -111,12 +106,13 @@ module.exports = class TransactionStateManager extends EventEmitter { } updateTx (txMeta, note) { + // validate txParams if (txMeta.txParams) { - Object.keys(txMeta.txParams).forEach((key) => { - const value = txMeta.txParams[key] - if (typeof value !== 'string') console.error(`${key}: ${value} in txParams is not a string`) - if (!ethUtil.isHexPrefixed(value)) console.error('is not hex prefixed, anything on txParams must be hex prefixed') - }) + if (typeof txMeta.txParams.data === 'undefined') { + delete txMeta.txParams.data + } + + this.validateTxParams(txMeta.txParams) } // create txMeta snapshot for history @@ -144,6 +140,23 @@ module.exports = class TransactionStateManager extends EventEmitter { this.updateTx(txMeta, `txStateManager#updateTxParams`) } + // validates txParams members by type + validateTxParams(txParams) { + Object.keys(txParams).forEach((key) => { + const value = txParams[key] + // validate types + switch (key) { + case 'chainId': + if (typeof value !== 'number' && typeof value !== 'string') throw new Error(`${key} in txParams is not a Number or hex string. got: (${value})`) + break + default: + if (typeof value !== 'string') throw new Error(`${key} in txParams is not a string. got: (${value})`) + if (!ethUtil.isHexPrefixed(value)) throw new Error(`${key} in txParams is not hex prefixed. got: (${value})`) + break + } + }) + } + /* Takes an object of fields to search for eg: let thingsToLookFor = { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 18d71874a..b96acc9da 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -49,7 +49,7 @@ module.exports = class MetamaskController extends EventEmitter { /** * @constructor - * @param {Object} opts + * @param {Object} opts */ constructor (opts) { super() @@ -57,7 +57,6 @@ module.exports = class MetamaskController extends EventEmitter { this.defaultMaxListeners = 20 this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200) - this.opts = opts const initState = opts.initState || {} this.recordFirstTimeInfo(initState) @@ -82,6 +81,7 @@ module.exports = class MetamaskController extends EventEmitter { // preferences controller this.preferencesController = new PreferencesController({ initState: initState.PreferencesController, + initLangCode: opts.initLangCode, }) // currency controller @@ -241,6 +241,11 @@ module.exports = class MetamaskController extends EventEmitter { static: { eth_syncing: false, web3_clientVersion: `MetaMask/v${version}`, + eth_sendTransaction: (payload, next, end) => { + const origin = payload.origin + const txParams = payload.params[0] + nodeify(this.txController.newUnapprovedTransaction, this.txController)(txParams, { origin }, end) + }, }, // account mgmt getAccounts: (cb) => { @@ -255,7 +260,6 @@ module.exports = class MetamaskController extends EventEmitter { cb(null, result) }, // tx signing - processTransaction: nodeify(async (txParams) => await this.txController.newUnapprovedTransaction(txParams), this), // old style msg signing processMessage: this.newUnsignedMessage.bind(this), // personal_sign msg signing @@ -296,8 +300,8 @@ module.exports = class MetamaskController extends EventEmitter { /** * The metamask-state of the various controllers, made available to the UI - * - * @returns {Object} status + * + * @returns {Object} status */ getState () { const wallet = this.configManager.getWallet() @@ -335,8 +339,8 @@ module.exports = class MetamaskController extends EventEmitter { /** * Returns an api-object which is consumed by the UI - * - * @returns {Object} + * + * @returns {Object} */ getApi () { const keyringController = this.keyringController @@ -351,6 +355,7 @@ module.exports = class MetamaskController extends EventEmitter { getState: (cb) => cb(null, this.getState()), setCurrentCurrency: this.setCurrentCurrency.bind(this), setUseBlockie: this.setUseBlockie.bind(this), + setCurrentLocale: this.setCurrentLocale.bind(this), markAccountsFound: this.markAccountsFound.bind(this), markPasswordForgotten: this.markPasswordForgotten.bind(this), unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this), @@ -365,7 +370,7 @@ module.exports = class MetamaskController extends EventEmitter { placeSeedWords: this.placeSeedWords.bind(this), verifySeedPhrase: nodeify(this.verifySeedPhrase, this), clearSeedWordCache: this.clearSeedWordCache.bind(this), - resetAccount: this.resetAccount.bind(this), + resetAccount: nodeify(this.resetAccount, this), importAccountWithStrategy: this.importAccountWithStrategy.bind(this), // vault management @@ -426,14 +431,14 @@ module.exports = class MetamaskController extends EventEmitter { /** * Creates a new Vault(?) and create a new keychain(?) - * + * * A vault is ... - * + * * A keychain is ... - * + * * * @param {} password - * + * * @returns {} vault */ async createNewVaultAndKeychain (password) { @@ -479,9 +484,9 @@ 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) { @@ -495,8 +500,8 @@ module.exports = class MetamaskController extends EventEmitter { // /** - * Adds a new account to ... - * + * Adds a new account to ... + * * @returns {} keyState */ async addNewAccount () { @@ -522,10 +527,10 @@ module.exports = class MetamaskController extends EventEmitter { /** * 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() @@ -540,7 +545,7 @@ module.exports = class MetamaskController extends EventEmitter { /** * 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. @@ -571,27 +576,32 @@ module.exports = class MetamaskController extends EventEmitter { /** * 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) { + async resetAccount (cb) { const selectedAddress = this.preferencesController.getSelectedAddress() this.txController.wipeTransactions(selectedAddress) - cb(null, selectedAddress) + + const networkController = this.networkController + const oldType = networkController.getProviderConfig().type + await networkController.setProviderType(oldType, true) + + return selectedAddress } /** * Imports an account ... ? - * + * * @param {} strategy * @param {} args * @param {} cb @@ -634,9 +644,9 @@ module.exports = class MetamaskController extends EventEmitter { } // Prefixed Style Message Signing Methods: - + /** - * + * * @param {} msgParams * @param {} cb */ @@ -655,7 +665,7 @@ module.exports = class MetamaskController extends EventEmitter { } }) } - + /** * @param {} msgParams */ @@ -676,7 +686,7 @@ module.exports = class MetamaskController extends EventEmitter { return this.getState() }) } - + /** * @param {} msgParams */ @@ -697,13 +707,13 @@ module.exports = class MetamaskController extends EventEmitter { return this.getState() }) } - + // --------------------------------------------------------------------------- // Account Restauration /** * ? - * + * * @param {} migratorOutput */ restoreOldVaultAccounts (migratorOutput) { @@ -714,7 +724,7 @@ module.exports = class MetamaskController extends EventEmitter { /** * ? - * + * * @param {} migratorOutput */ restoreOldLostAccounts (migratorOutput) { @@ -728,9 +738,9 @@ module.exports = class MetamaskController extends EventEmitter { /** * 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. */ @@ -823,7 +833,7 @@ module.exports = class MetamaskController extends EventEmitter { if (cb && typeof cb === 'function') { cb(null, this.getState()) } - } + } cancelPersonalMessage (msgId, cb) { const messageManager = this.personalMessageManager @@ -978,7 +988,7 @@ module.exports = class MetamaskController extends EventEmitter { const percentileNum = percentile(50, lowestPrices) const percentileNumBn = new BN(percentileNum) return '0x' + percentileNumBn.mul(GWEI_BN).toString(16) - } + } //============================================================================= // CONFIG @@ -1029,6 +1039,15 @@ module.exports = class MetamaskController extends EventEmitter { } } + setCurrentLocale (key, cb) { + try { + this.preferencesController.setCurrentLocale(key) + cb(null) + } catch (err) { + cb(err) + } + } + recordFirstTimeInfo (initState) { if (!('firstTimeInfo' in initState)) { initState.firstTimeInfo = { diff --git a/app/scripts/migrations/013.js b/app/scripts/migrations/013.js index 8f11e510e..15a9b28d4 100644 --- a/app/scripts/migrations/013.js +++ b/app/scripts/migrations/013.js @@ -27,8 +27,11 @@ module.exports = { function transformState (state) { const newState = state - if (newState.config.provider.type === 'testnet') { - newState.config.provider.type = 'ropsten' + const { config } = newState + if ( config && config.provider ) { + if (config.provider.type === 'testnet') { + newState.config.provider.type = 'ropsten' + } } return newState } diff --git a/app/scripts/migrations/015.js b/app/scripts/migrations/015.js index 4b839580b..5e2f9e63b 100644 --- a/app/scripts/migrations/015.js +++ b/app/scripts/migrations/015.js @@ -28,11 +28,14 @@ module.exports = { function transformState (state) { const newState = state - const transactions = newState.TransactionController.transactions - newState.TransactionController.transactions = transactions.map((txMeta) => { - if (!txMeta.err) return txMeta - else if (txMeta.err.message === 'Gave up submitting tx.') txMeta.status = 'failed' - return txMeta - }) + const { TransactionController } = newState + if (TransactionController && TransactionController.transactions) { + const transactions = TransactionController.transactions + newState.TransactionController.transactions = transactions.map((txMeta) => { + if (!txMeta.err) return txMeta + else if (txMeta.err.message === 'Gave up submitting tx.') txMeta.status = 'failed' + return txMeta + }) + } return newState } diff --git a/app/scripts/migrations/016.js b/app/scripts/migrations/016.js index 4fc534f1c..048c7a40e 100644 --- a/app/scripts/migrations/016.js +++ b/app/scripts/migrations/016.js @@ -28,14 +28,18 @@ module.exports = { function transformState (state) { const newState = state - const transactions = newState.TransactionController.transactions - newState.TransactionController.transactions = transactions.map((txMeta) => { - if (!txMeta.err) return txMeta - if (txMeta.err === 'transaction with the same hash was already imported.') { - txMeta.status = 'submitted' - delete txMeta.err - } - return txMeta - }) + const { TransactionController } = newState + if (TransactionController && TransactionController.transactions) { + const transactions = newState.TransactionController.transactions + + newState.TransactionController.transactions = transactions.map((txMeta) => { + if (!txMeta.err) return txMeta + if (txMeta.err === 'transaction with the same hash was already imported.') { + txMeta.status = 'submitted' + delete txMeta.err + } + return txMeta + }) + } return newState } diff --git a/app/scripts/migrations/017.js b/app/scripts/migrations/017.js index 24959cd3a..5f6d906d6 100644 --- a/app/scripts/migrations/017.js +++ b/app/scripts/migrations/017.js @@ -27,14 +27,17 @@ module.exports = { function transformState (state) { const newState = state - const transactions = newState.TransactionController.transactions - newState.TransactionController.transactions = transactions.map((txMeta) => { - if (!txMeta.status === 'failed') return txMeta - if (txMeta.retryCount > 0 && txMeta.retryCount < 2) { - txMeta.status = 'submitted' - delete txMeta.err - } - return txMeta - }) + const { TransactionController } = newState + if (TransactionController && TransactionController.transactions) { + const transactions = newState.TransactionController.transactions + newState.TransactionController.transactions = transactions.map((txMeta) => { + if (!txMeta.status === 'failed') return txMeta + if (txMeta.retryCount > 0 && txMeta.retryCount < 2) { + txMeta.status = 'submitted' + delete txMeta.err + } + return txMeta + }) + } return newState } diff --git a/app/scripts/migrations/018.js b/app/scripts/migrations/018.js index d27fe3f46..bea1fe3da 100644 --- a/app/scripts/migrations/018.js +++ b/app/scripts/migrations/018.js @@ -29,24 +29,27 @@ module.exports = { function transformState (state) { const newState = state - const transactions = newState.TransactionController.transactions - newState.TransactionController.transactions = transactions.map((txMeta) => { - // no history: initialize - if (!txMeta.history || txMeta.history.length === 0) { - const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta) - txMeta.history = [snapshot] + const { TransactionController } = newState + if (TransactionController && TransactionController.transactions) { + const transactions = newState.TransactionController.transactions + newState.TransactionController.transactions = transactions.map((txMeta) => { + // no history: initialize + if (!txMeta.history || txMeta.history.length === 0) { + const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta) + txMeta.history = [snapshot] + return txMeta + } + // has history: migrate + const newHistory = ( + txStateHistoryHelper.migrateFromSnapshotsToDiffs(txMeta.history) + // remove empty diffs + .filter((entry) => { + return !Array.isArray(entry) || entry.length > 0 + }) + ) + txMeta.history = newHistory return txMeta - } - // has history: migrate - const newHistory = ( - txStateHistoryHelper.migrateFromSnapshotsToDiffs(txMeta.history) - // remove empty diffs - .filter((entry) => { - return !Array.isArray(entry) || entry.length > 0 - }) - ) - txMeta.history = newHistory - return txMeta - }) + }) + } return newState } diff --git a/app/scripts/migrations/019.js b/app/scripts/migrations/019.js index 072c96370..ce5da6859 100644 --- a/app/scripts/migrations/019.js +++ b/app/scripts/migrations/019.js @@ -29,32 +29,36 @@ module.exports = { function transformState (state) { const newState = state - const transactions = newState.TransactionController.transactions + const { TransactionController } = newState + if (TransactionController && TransactionController.transactions) { - newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => { - if (txMeta.status !== 'submitted') return txMeta + const transactions = newState.TransactionController.transactions - const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed') - .filter((tx) => tx.txParams.from === txMeta.txParams.from) - .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from) - const highestConfirmedNonce = getHighestNonce(confirmedTxs) + newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => { + if (txMeta.status !== 'submitted') return txMeta - const pendingTxs = txList.filter((tx) => tx.status === 'submitted') - .filter((tx) => tx.txParams.from === txMeta.txParams.from) - .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from) - const highestContinuousNonce = getHighestContinuousFrom(pendingTxs, highestConfirmedNonce) + const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed') + .filter((tx) => tx.txParams.from === txMeta.txParams.from) + .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from) + const highestConfirmedNonce = getHighestNonce(confirmedTxs) - const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce) + const pendingTxs = txList.filter((tx) => tx.status === 'submitted') + .filter((tx) => tx.txParams.from === txMeta.txParams.from) + .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from) + const highestContinuousNonce = getHighestContinuousFrom(pendingTxs, highestConfirmedNonce) - if (parseInt(txMeta.txParams.nonce, 16) > maxNonce + 1) { - txMeta.status = 'failed' - txMeta.err = { - message: 'nonce too high', - note: 'migration 019 custom error', + const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce) + + if (parseInt(txMeta.txParams.nonce, 16) > maxNonce + 1) { + txMeta.status = 'failed' + txMeta.err = { + message: 'nonce too high', + note: 'migration 019 custom error', + } } - } - return txMeta - }) + return txMeta + }) + } return newState } diff --git a/app/scripts/migrations/022.js b/app/scripts/migrations/022.js index c3c0d53ef..1fbe241e6 100644 --- a/app/scripts/migrations/022.js +++ b/app/scripts/migrations/022.js @@ -28,12 +28,15 @@ module.exports = { function transformState (state) { const newState = state - const transactions = newState.TransactionController.transactions - - newState.TransactionController.transactions = transactions.map((txMeta) => { - if (txMeta.status !== 'submitted' || txMeta.submittedTime) return txMeta - txMeta.submittedTime = (new Date()).getTime() - return txMeta - }) + const { TransactionController } = newState + if (TransactionController && TransactionController.transactions) { + const transactions = newState.TransactionController.transactions + + newState.TransactionController.transactions = transactions.map((txMeta) => { + if (txMeta.status !== 'submitted' || txMeta.submittedTime) return txMeta + txMeta.submittedTime = (new Date()).getTime() + return txMeta + }) + } return newState } diff --git a/app/scripts/migrations/023.js b/app/scripts/migrations/023.js new file mode 100644 index 000000000..151496b06 --- /dev/null +++ b/app/scripts/migrations/023.js @@ -0,0 +1,54 @@ + +const version = 23 + +/* + +This migration removes transactions that are no longer usefull down to 40 total + +*/ + +const clone = require('clone') + +module.exports = { + version, + + migrate: function (originalVersionedData) { + const versionedData = clone(originalVersionedData) + versionedData.meta.version = version + try { + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + } catch (err) { + console.warn(`MetaMask Migration #${version}` + err.stack) + } + return Promise.resolve(versionedData) + }, +} + +function transformState (state) { + const newState = state + + const { TransactionController } = newState + if (TransactionController && TransactionController.transactions) { + const transactions = newState.TransactionController.transactions + + if (transactions.length <= 40) return newState + + let reverseTxList = transactions.reverse() + let stripping = true + while (reverseTxList.length > 40 && stripping) { + let txIndex = reverseTxList.findIndex((txMeta) => { + return (txMeta.status === 'failed' || + txMeta.status === 'rejected' || + txMeta.status === 'confirmed' || + txMeta.status === 'dropped') + }) + if (txIndex < 0) stripping = false + else reverseTxList.splice(txIndex, 1) + } + + newState.TransactionController.transactions = reverseTxList.reverse() + } + return newState +} diff --git a/app/scripts/migrations/024.js b/app/scripts/migrations/024.js new file mode 100644 index 000000000..d0b276a79 --- /dev/null +++ b/app/scripts/migrations/024.js @@ -0,0 +1,41 @@ + +const version = 24 + +/* + +This migration ensures that the from address in txParams is to lower case for +all unapproved transactions + +*/ + +const clone = require('clone') + +module.exports = { + version, + + migrate: async function (originalVersionedData) { + const versionedData = clone(originalVersionedData) + versionedData.meta.version = version + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + return versionedData + }, +} + +function transformState (state) { + const newState = state + if (!newState.TransactionController) return newState + const transactions = newState.TransactionController.transactions + newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => { + if ( + txMeta.status === 'unapproved' && + txMeta.txParams && + txMeta.txParams.from + ) { + txMeta.txParams.from = txMeta.txParams.from.toLowerCase() + } + return txMeta + }) + return newState +} diff --git a/app/scripts/migrations/025.js b/app/scripts/migrations/025.js new file mode 100644 index 000000000..fc3b20a44 --- /dev/null +++ b/app/scripts/migrations/025.js @@ -0,0 +1,61 @@ +// next version number +const version = 25 + +/* + +normalizes txParams on unconfirmed txs + +*/ +const ethUtil = require('ethereumjs-util') +const clone = require('clone') + +module.exports = { + version, + + migrate: async function (originalVersionedData) { + const versionedData = clone(originalVersionedData) + versionedData.meta.version = version + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + return versionedData + }, +} + +function transformState (state) { + const newState = state + + if (newState.TransactionController) { + if (newState.TransactionController.transactions) { + const transactions = newState.TransactionController.transactions + newState.TransactionController.transactions = transactions.map((txMeta) => { + if (txMeta.status !== 'unapproved') return txMeta + txMeta.txParams = normalizeTxParams(txMeta.txParams) + return txMeta + }) + } + } + + return newState +} + +function normalizeTxParams (txParams) { + // functions that handle normalizing of that key in txParams + const whiteList = { + from: from => ethUtil.addHexPrefix(from).toLowerCase(), + to: to => ethUtil.addHexPrefix(txParams.to).toLowerCase(), + nonce: nonce => ethUtil.addHexPrefix(nonce), + value: value => ethUtil.addHexPrefix(value), + data: data => ethUtil.addHexPrefix(data), + gas: gas => ethUtil.addHexPrefix(gas), + gasPrice: gasPrice => ethUtil.addHexPrefix(gasPrice), + } + + // apply only keys in the whiteList + const normalizedTxParams = {} + Object.keys(whiteList).forEach((key) => { + if (txParams[key]) normalizedTxParams[key] = whiteList[key](txParams[key]) + }) + + return normalizedTxParams +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index b49a40c65..6c4a51b32 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -33,4 +33,7 @@ module.exports = [ require('./020'), require('./021'), require('./022'), + require('./023'), + require('./024'), + require('./025'), ] diff --git a/app/scripts/migrations/template.js b/app/scripts/migrations/template.js new file mode 100644 index 000000000..0915c6bdf --- /dev/null +++ b/app/scripts/migrations/template.js @@ -0,0 +1,29 @@ +// next version number +const version = 0 + +/* + +description of migration and what it does + +*/ + +const clone = require('clone') + +module.exports = { + version, + + migrate: async function (originalVersionedData) { + const versionedData = clone(originalVersionedData) + versionedData.meta.version = version + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + return versionedData + }, +} + +function transformState (state) { + const newState = state + // transform state here + return newState +} diff --git a/app/scripts/popup.js b/app/scripts/popup.js deleted file mode 100644 index e78981f06..000000000 --- a/app/scripts/popup.js +++ /dev/null @@ -1,78 +0,0 @@ -const injectCss = require('inject-css') -const OldMetaMaskUiCss = require('../../old-ui/css') -const NewMetaMaskUiCss = require('../../ui/css') -const startPopup = require('./popup-core') -const PortStream = require('./lib/port-stream.js') -const isPopupOrNotification = require('./lib/is-popup-or-notification') -const extension = require('extensionizer') -const ExtensionPlatform = require('./platforms/extension') -const NotificationManager = require('./lib/notification-manager') -const notificationManager = new NotificationManager() -const setupRaven = require('./lib/setupRaven') - -// create platform global -global.platform = new ExtensionPlatform() - -// setup sentry error reporting -const release = global.platform.getVersion() -setupRaven({ release }) - -// inject css -// const css = MetaMaskUiCss() -// injectCss(css) - -// identify window type (popup, notification) -const windowType = isPopupOrNotification() -global.METAMASK_UI_TYPE = windowType -closePopupIfOpen(windowType) - -// setup stream to background -const extensionPort = extension.runtime.connect({ name: windowType }) -const connectionStream = new PortStream(extensionPort) - -// start ui -const container = document.getElementById('app-content') -startPopup({ container, connectionStream }, (err, store) => { - if (err) return displayCriticalError(err) - - // Code commented out until we begin auto adding users to NewUI - // const { isMascara, identities = {}, featureFlags = {} } = store.getState().metamask - // const firstTime = Object.keys(identities).length === 0 - const { isMascara, featureFlags = {} } = store.getState().metamask - let betaUIState = featureFlags.betaUI - - // Code commented out until we begin auto adding users to NewUI - // const useBetaCss = isMascara || firstTime || betaUIState - const useBetaCss = isMascara || betaUIState - - let css = useBetaCss ? NewMetaMaskUiCss() : OldMetaMaskUiCss() - let deleteInjectedCss = injectCss(css) - let newBetaUIState - - store.subscribe(() => { - const state = store.getState() - newBetaUIState = state.metamask.featureFlags.betaUI - if (newBetaUIState !== betaUIState) { - deleteInjectedCss() - betaUIState = newBetaUIState - css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss() - deleteInjectedCss = injectCss(css) - } - if (state.appState.shouldClose) notificationManager.closePopup() - }) -}) - - -function closePopupIfOpen (windowType) { - if (windowType !== 'notification') { - // should close only chrome popup - notificationManager.closePopup() - } -} - -function displayCriticalError (err) { - container.innerHTML = '<div class="critical-error">The MetaMask app failed to load: please open and close MetaMask again to restart.</div>' - container.style.height = '80px' - log.error(err.stack) - throw err -} diff --git a/app/scripts/ui.js b/app/scripts/ui.js new file mode 100644 index 000000000..13c7ac5ec --- /dev/null +++ b/app/scripts/ui.js @@ -0,0 +1,84 @@ +const injectCss = require('inject-css') +const OldMetaMaskUiCss = require('../../old-ui/css') +const NewMetaMaskUiCss = require('../../ui/css') +const startPopup = require('./popup-core') +const PortStream = require('./lib/port-stream.js') +const isPopupOrNotification = require('./lib/is-popup-or-notification') +const extension = require('extensionizer') +const ExtensionPlatform = require('./platforms/extension') +const NotificationManager = require('./lib/notification-manager') +const notificationManager = new NotificationManager() +const setupRaven = require('./lib/setupRaven') + +start().catch(log.error) + +async function start() { + + // create platform global + global.platform = new ExtensionPlatform() + + // setup sentry error reporting + const release = global.platform.getVersion() + setupRaven({ release }) + + // inject css + // const css = MetaMaskUiCss() + // injectCss(css) + + // identify window type (popup, notification) + const windowType = isPopupOrNotification() + global.METAMASK_UI_TYPE = windowType + closePopupIfOpen(windowType) + + // setup stream to background + const extensionPort = extension.runtime.connect({ name: windowType }) + const connectionStream = new PortStream(extensionPort) + + // start ui + const container = document.getElementById('app-content') + startPopup({ container, connectionStream }, (err, store) => { + if (err) return displayCriticalError(err) + + // Code commented out until we begin auto adding users to NewUI + // const { isMascara, identities = {}, featureFlags = {} } = store.getState().metamask + // const firstTime = Object.keys(identities).length === 0 + const { isMascara, featureFlags = {} } = store.getState().metamask + let betaUIState = featureFlags.betaUI + + // Code commented out until we begin auto adding users to NewUI + // const useBetaCss = isMascara || firstTime || betaUIState + const useBetaCss = isMascara || betaUIState + + let css = useBetaCss ? NewMetaMaskUiCss() : OldMetaMaskUiCss() + let deleteInjectedCss = injectCss(css) + let newBetaUIState + + store.subscribe(() => { + const state = store.getState() + newBetaUIState = state.metamask.featureFlags.betaUI + if (newBetaUIState !== betaUIState) { + deleteInjectedCss() + betaUIState = newBetaUIState + css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss() + deleteInjectedCss = injectCss(css) + } + if (state.appState.shouldClose) notificationManager.closePopup() + }) + }) + + + function closePopupIfOpen (windowType) { + if (windowType !== 'notification') { + // should close only chrome popup + notificationManager.closePopup() + } + } + + function displayCriticalError (err) { + container.innerHTML = '<div class="critical-error">The MetaMask app failed to load: please open and close MetaMask again to restart.</div>' + container.style.height = '80px' + log.error(err.stack) + throw err + } + +} |