diff options
author | brunobar79 <brunobar79@gmail.com> | 2018-11-14 06:21:15 +0800 |
---|---|---|
committer | brunobar79 <brunobar79@gmail.com> | 2018-11-14 06:21:15 +0800 |
commit | 484aa6801ea50fb08253fd08559710c53e0c189d (patch) | |
tree | 2a7876d3800e57aafab39a6beebe00abf7fc26ba | |
parent | c651212025ab7cd0309d96be6687c1eba35ab9fa (diff) | |
parent | 0549782595335e8257d1b4abf7d6220020e2c8db (diff) | |
download | tangerine-wallet-browser-484aa6801ea50fb08253fd08559710c53e0c189d.tar.gz tangerine-wallet-browser-484aa6801ea50fb08253fd08559710c53e0c189d.tar.zst tangerine-wallet-browser-484aa6801ea50fb08253fd08559710c53e0c189d.zip |
Merge branch 'develop' into trezor-v5
46 files changed, 4603 insertions, 479 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 041803eae..69dffc7b8 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -5,7 +5,9 @@ about: Using MetaMask, but it's not working as you expect? --- <!-- -BEFORE SUBMITTING: PLEASE SEARCH TO MAKE SURE THIS ISSUE HAS NOT BEEN SUBMITTED +BEFORE SUBMITTING: +1) Please search to make sure this issue has not been opened already +2) If this is a implementation question or trouble with your personal project, please post on StackExchange. This will get your question answered more quickly and make it easier for other devs to find the answer in the future. --> **Describe the bug** @@ -25,10 +27,9 @@ A clear description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Browser details (please complete the following information):** - - OS: [e.g. iOS] + - OS: [e.g. OS X, Windows] - Browser [e.g. chrome, safari] - - MetaMask Version [e.g. 4.9.0] - - Old UI or New / Beta UI? + - MetaMask Version [e.g. 5.0.2] **Additional context** Add any other context about the problem here. diff --git a/CHANGELOG.md b/CHANGELOG.md index 7142b815b..85ac342dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## Current Develop Branch +## 5.0.2 Friday November 9 2018 + +- Fixed bug that caused accounts to update slowly to sites. #5717 +- Fixed bug that could lead to some sites crashing. #5709 + +## 5.0.1 Wednesday November 7 2018 + +- Fixed bug in privacy mode that made it not work correctly on Firefox. + ## 5.0.0 Tuesday November 6 2018 - Implements EIP 1102 as a user-activated "Privacy Mode". @@ -3,7 +3,7 @@ ## Support -If you're a user seeking support, [here is our support site](https://metamask.helpscoutdocs.com/). +If you're a user seeking support, [here is our support site](https://metamask.zendesk.com/hc/en-us). ## Introduction diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 95c9efeeb..7a70937a3 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -785,6 +785,9 @@ "noWebcamFound": { "message": "Your computer's webcam was not found. Please try again." }, + "ofTextNofM": { + "message": "of" + }, "oldUI": { "message": "Old UI" }, @@ -932,6 +935,9 @@ "restoreAccountWithSeed": { "message": "Restore your Account with Seed Phrase" }, + "requestsAwaitingAcknowledgement": { + "message": "requests waiting to be acknowledged" + }, "required": { "message": "Required" }, diff --git a/app/_locales/index.json b/app/_locales/index.json index 234215e39..30fb87faf 100644 --- a/app/_locales/index.json +++ b/app/_locales/index.json @@ -14,10 +14,11 @@ { "code": "pl", "name": "Polskie" }, { "code": "pt", "name": "Português" }, { "code": "ru", "name": "Русский" }, + { "code": "sk", "name": "Slovenčina" }, { "code": "sl", "name": "Slovenščina" }, { "code": "th", "name": "ไทย" }, { "code": "tml", "name": "தமிழ்" }, - { "code": "tr", "name": "Türkçe" }, + { "code": "tr", "name": "Türkçe" }, { "code": "vi", "name": "Tiếng Việt" }, { "code": "zh_CN", "name": "中文(简体)" }, { "code": "zh_TW", "name": "中文(繁體)" } diff --git a/app/_locales/sk/messages.json b/app/_locales/sk/messages.json new file mode 100644 index 000000000..fde553c9e --- /dev/null +++ b/app/_locales/sk/messages.json @@ -0,0 +1,957 @@ +{ + "privacyMode": { + "message": "Režim súkromia" + }, + "privacyModeDescription": { + "message": "Webové stránky musia požiadať o prístup k zobrazeniu informácií o vašom účte." + }, + "exposeAccounts": { + "message": "Vystavte účty" + }, + "exposeDescription": { + "message": "Vystavte účty na aktuální webové stránky. Užitečné pro starší dappy." + }, + "confirmExpose": { + "message": "Opravdu chcete své účty vystavit na stávajícím webu?" + }, + "confirmClear": { + "message": "Naozaj chcete vymazať schválené webové stránky?" + }, + "clearApprovalDataSuccess": { + "message": "Schválené údaje webových stránek byly úspěšně zrušeny." + }, + "approvalData": { + "message": "Údaje o schválení" + }, + "approvalDataDescription": { + "message": "Vymažte schválené údaje webových stránek, aby všechny weby znovu požádaly o schválení." + }, + "clearApprovalData": { + "message": "Jasné údaje o schválení" + }, + "approve": { + "message": "Schválit" + }, + "reject": { + "message": "Odmítnout" + }, + "providerAPIRequest": { + "message": "Požadavek API Ethereum" + }, + "reviewProviderRequest": { + "message": "Přečtěte si prosím tuto žádost API Ethereum." + }, + "providerRequestInfo": { + "message": "Níže uvedená doména se pokouší požádat o přístup k API Ethereum, aby mohla komunikovat s blokádou Ethereum. Před schválením přístupu Ethereum vždy zkontrolujte, zda jste na správném místě." + }, + "accept": { + "message": "Přijmout" + }, + "account": { + "message": "Účet" + }, + "accountDetails": { + "message": "Detaily účtu" + }, + "accountName": { + "message": "Název účtu" + }, + "address": { + "message": "Adresa" + }, + "addCustomToken": { + "message": "Přidat vlastní token" + }, + "addToken": { + "message": "Přidat token" + }, + "addTokens": { + "message": "Přidat tokeny" + }, + "amount": { + "message": "Částka" + }, + "amountPlusGas": { + "message": "Částka + palivo" + }, + "appDescription": { + "message": "Ethereum rozšíření prohlížeče", + "description": "The description of the application" + }, + "appName": { + "message": "MetaMask", + "description": "The name of the application" + }, + "approved": { + "message": "Schváleno" + }, + "attemptingConnect": { + "message": "Pokouším se připojit k blockchainu." + }, + "attributions": { + "message": "Zásluhy" + }, + "available": { + "message": "Dostupné" + }, + "back": { + "message": "Zpět" + }, + "balance": { + "message": "Zůstatek:" + }, + "balances": { + "message": "Zůstatek tokenu" + }, + "balanceIsInsufficientGas": { + "message": "Nedostatek prostředků pro aktuální množství paliva" + }, + "beta": { + "message": "BETA" + }, + "betweenMinAndMax": { + "message": "musí být větší nebo roven $1 a menší nebo roven $2.", + "description": "helper for inputting hex as decimal input" + }, + "blockiesIdenticon": { + "message": "Použít Blockies Identicon" + }, + "borrowDharma": { + "message": "Pújčit si přes Dharma (Beta)" + }, + "builtInCalifornia": { + "message": "MetaMask je navržen a vytvořen v Kalifornii." + }, + "buy": { + "message": "Koupit" + }, + "buyCoinbase": { + "message": "Nákup na Coinbase" + }, + "buyCoinbaseExplainer": { + "message": "Coinbase je světově nejoblíbenější místo k nákupu a prodeji bitcoinu, etherea nebo litecoinu." + }, + "ok": { + "message": "Ok" + }, + "cancel": { + "message": "Zrušit" + }, + "classicInterface": { + "message": "Použít klasické rozhraní" + }, + "clickCopy": { + "message": "Kliknutím zkopírovat" + }, + "confirm": { + "message": "Potvrdit" + }, + "confirmed": { + "message": "Potvrzeno" + }, + "confirmContract": { + "message": "Potvrdit kontrakt" + }, + "confirmPassword": { + "message": "Potvrdit heslo" + }, + "confirmTransaction": { + "message": "Potvrdit transakci" + }, + "continue": { + "message": "Pokračovat" + }, + "continueToCoinbase": { + "message": "Přejít na Coinbase" + }, + "contractDeployment": { + "message": "Nasazení kontraktu" + }, + "conversionProgress": { + "message": "Provádí se převod" + }, + "copiedButton": { + "message": "Zkopírováno" + }, + "copiedClipboard": { + "message": "Zkopírováno do schránky" + }, + "copiedExclamation": { + "message": "Zkopírováno!" + }, + "copiedSafe": { + "message": "Zkopíroval jsem to na bezpečné místo" + }, + "copy": { + "message": "Kopírovat" + }, + "copyToClipboard": { + "message": "Kopírovat do schránky" + }, + "copyButton": { + "message": " Kopírovat " + }, + "copyPrivateKey": { + "message": "Toto je váš privátní klíč (kliknutím zkopírujte)" + }, + "create": { + "message": "Vytvořit" + }, + "createAccount": { + "message": "Vytvořit účet" + }, + "createDen": { + "message": "Vytvořit" + }, + "crypto": { + "message": "Krypto", + "description": "Exchange type (cryptocurrencies)" + }, + "currentConversion": { + "message": "Aktuální převod" + }, + "currentNetwork": { + "message": "Aktuální síť" + }, + "customGas": { + "message": "Nastavit palivo" + }, + "customToken": { + "message": "Vlastní token" + }, + "customize": { + "message": "Nastavit" + }, + "customRPC": { + "message": "Vlastní RPC" + }, + "decimalsMustZerotoTen": { + "message": "Desetinných míst musí být od 0 do 36." + }, + "decimal": { + "message": "Počet desetinných míst přesnosti" + }, + "defaultNetwork": { + "message": "Výchozí síť pro Etherové transakce je Main Net." + }, + "denExplainer": { + "message": "Váš DEN je heslem šifrované uložiště v MetaMasku." + }, + "deposit": { + "message": "Vklad" + }, + "depositBTC": { + "message": "Vložte BTC na níže uvedenou adresu:" + }, + "depositCoin": { + "message": "Vložte $1 na níže uvedenou adresu", + "description": "Tells the user what coin they have selected to deposit with shapeshift" + }, + "depositEth": { + "message": "Vložit Eth" + }, + "depositEther": { + "message": "Vložit Ether" + }, + "depositFiat": { + "message": "Vklad s fiat měnou" + }, + "depositFromAccount": { + "message": "Vložte z jiného účtu" + }, + "depositShapeShift": { + "message": "Vklad přes ShapeShift" + }, + "depositShapeShiftExplainer": { + "message": "Pokud vlastníte jiné kryptoměny, můžete je směnit Ether a vložit ho přímo do peněženky MetaMask. Bez založení účtu." + }, + "details": { + "message": "Podrobnosti" + }, + "directDeposit": { + "message": "Přímý vklad" + }, + "directDepositEther": { + "message": "Vložit Ether přímo" + }, + "directDepositEtherExplainer": { + "message": "Pokud už vlastníte nějaký Ether, nejrychleji ho dostanete do peněženky přímým vkladem." + }, + "done": { + "message": "Hotovo" + }, + "downloadStateLogs": { + "message": "Stáhnout stavové protokoly" + }, + "dropped": { + "message": "Zrušeno" + }, + "edit": { + "message": "Upravit" + }, + "editAccountName": { + "message": "Upravit název účtu" + }, + "emailUs": { + "message": "Napište nám e-mail!" + }, + "encryptNewDen": { + "message": "Zašifrujte svůj nový DEN" + }, + "enterPassword": { + "message": "Zadejte heslo" + }, + "enterPasswordConfirm": { + "message": "Zadejte heslo k potvrzení" + }, + "passwordNotLongEnough": { + "message": "Heslo není dost dlouhé" + }, + "passwordsDontMatch": { + "message": "Hesla nejsou stejná" + }, + "etherscanView": { + "message": "Prohlédněte si účet na Etherscan" + }, + "exchangeRate": { + "message": "Směnný kurz" + }, + "exportPrivateKey": { + "message": "Exportovat privátní klíč" + }, + "exportPrivateKeyWarning": { + "message": "Exportujte privátní klíč na vlastní riziko." + }, + "failed": { + "message": "Neúspěšné" + }, + "fiat": { + "message": "FIAT", + "description": "Exchange type" + }, + "fileImportFail": { + "message": "Import souboru nefunguje? Klikněte sem!", + "description": "Helps user import their account from a JSON file" + }, + "followTwitter": { + "message": "Sledujte nás na Twitteru" + }, + "from": { + "message": "Od" + }, + "fromToSame": { + "message": "Adresy odesílatele a příjemce nemohou být stejné" + }, + "fromShapeShift": { + "message": "Z ShapeShift" + }, + "gas": { + "message": "Palivo", + "description": "Short indication of gas cost" + }, + "gasFee": { + "message": "Poplatek za palivo" + }, + "gasLimit": { + "message": "Limit paliva" + }, + "gasLimitCalculation": { + "message": "Počítáme doporučený limit paliva na základě úspěšnosti v síti." + }, + "gasLimitRequired": { + "message": "Limit paliva je povinný" + }, + "gasLimitTooLow": { + "message": "Limit paliva musí být alespoň 21000" + }, + "generatingSeed": { + "message": "Generuji klíčovou frázi..." + }, + "gasPrice": { + "message": "Cena paliva (GWEI)" + }, + "gasPriceCalculation": { + "message": "Počítáme doporučenou cenu paliva na základě úspěšnosti v síti." + }, + "gasPriceRequired": { + "message": "Cena paliva je povinná" + }, + "getEther": { + "message": "Získejte Ether" + }, + "getEtherFromFaucet": { + "message": "Získejte Ether z faucetu za $1.", + "description": "Displays network name for Ether faucet" + }, + "greaterThanMin": { + "message": "musí být větší nebo roven $1.", + "description": "helper for inputting hex as decimal input" + }, + "here": { + "message": "zde", + "description": "as in -click here- for more information (goes with troubleTokenBalances)" + }, + "hereList": { + "message": "Tady je seznam!!!!" + }, + "hide": { + "message": "Skrýt" + }, + "hideToken": { + "message": "Skrýt token" + }, + "hideTokenPrompt": { + "message": "Skrýt token?" + }, + "howToDeposit": { + "message": "Jakým způsobem chcete vložit Ether?" + }, + "holdEther": { + "message": "Dovoluje vám držet ether a tokeny a slouží jako most k decentralizovaným aplikacím." + }, + "import": { + "message": "Import", + "description": "Button to import an account from a selected file" + }, + "importAccount": { + "message": "Import účtu" + }, + "importAccountMsg": { + "message": "Importované účty nebudou spojeny s vaší původní MetaMaskovou klíčovou frází. Zjistěte více o importovaných účtech " + }, + "importAnAccount": { + "message": "Import účtu" + }, + "importDen": { + "message": "Import existujícího DEN" + }, + "imported": { + "message": "Importováno", + "description": "status showing that an account has been fully loaded into the keyring" + }, + "infoHelp": { + "message": "Informace a nápověda" + }, + "insufficientFunds": { + "message": "Nedostatek finančních prostředků." + }, + "insufficientTokens": { + "message": "Nedostatek tokenů." + }, + "invalidAddress": { + "message": "Neplatná adresa" + }, + "invalidAddressRecipient": { + "message": "Adresa příjemce je neplatná" + }, + "invalidGasParams": { + "message": "Neplatná parametry paliva" + }, + "invalidInput": { + "message": "Neplatný vstup." + }, + "invalidRequest": { + "message": "Neplatný požadavek" + }, + "invalidRPC": { + "message": "Neplatné RPC URI" + }, + "jsonFail": { + "message": "Něco se pokazilo. Prosím, ujistěte se, že váš JSON soubor má správný formát." + }, + "jsonFile": { + "message": "JSON soubor", + "description": "format for importing an account" + }, + "keepTrackTokens": { + "message": "Udržujte si záznamy o tokenech, které jste koupili s účtem v MetaMasku." + }, + "kovan": { + "message": "Kovan Test Network" + }, + "knowledgeDataBase": { + "message": "Navštivte naši Knowledge Base" + }, + "max": { + "message": "Max" + }, + "learnMore": { + "message": "Zjistěte více." + }, + "lessThanMax": { + "message": "musí být menší nebo roven $1.", + "description": "helper for inputting hex as decimal input" + }, + "likeToAddTokens": { + "message": "Chcete přidat tyto tokeny?" + }, + "links": { + "message": "Odkazy" + }, + "limit": { + "message": "Limit" + }, + "loading": { + "message": "Načítám..." + }, + "loadingTokens": { + "message": "Načítám tokeny..." + }, + "localhost": { + "message": "Localhost 8545" + }, + "login": { + "message": "Přihlásit" + }, + "logout": { + "message": "Odhlásit" + }, + "loose": { + "message": "Nevázané" + }, + "loweCaseWords": { + "message": "slova klíčové fráze mají pouze malá písmena" + }, + "mainnet": { + "message": "Main Ethereum Network" + }, + "message": { + "message": "Zpráva" + }, + "metamaskDescription": { + "message": "MetaMask je bezpečný osobní trezor pro Ethereum." + }, + "min": { + "message": "Minimum" + }, + "myAccounts": { + "message": "Moje účty" + }, + "mustSelectOne": { + "message": "Musíte zvolit aspoň 1 token." + }, + "needEtherInWallet": { + "message": "Potřebujete Ether v peněžence, abyste mohli pomocí MetaMasku interagovat s decentralizovanými aplikacemi." + }, + "needImportFile": { + "message": "Musíte zvolit soubor k importu.", + "description": "User is important an account and needs to add a file to continue" + }, + "needImportPassword": { + "message": "Musíte zadat heslo pro zvolený soubor.", + "description": "Password and file needed to import an account" + }, + "negativeETH": { + "message": "Nelze odeslat zápornou částku ETH." + }, + "networks": { + "message": "Sítě" + }, + "newAccount": { + "message": "Nový účet" + }, + "newAccountNumberName": { + "message": "Účet $1", + "description": "Default name of next account to be created on create account screen" + }, + "newContract": { + "message": "Nový kontrakt" + }, + "newPassword": { + "message": "Nové heslo (min 8 znaků)" + }, + "newRecipient": { + "message": "Nový příjemce" + }, + "newRPC": { + "message": "Nová RPC URL" + }, + "next": { + "message": "Další" + }, + "noAddressForName": { + "message": "Pro toto jméno nebyla nastavena žádná adresa." + }, + "noDeposits": { + "message": "Žádný vklad" + }, + "noTransactionHistory": { + "message": "Žádná historie transakcí." + }, + "noTransactions": { + "message": "Žádné transakce" + }, + "notStarted": { + "message": "Nezačalo" + }, + "oldUI": { + "message": "Staré rozhraní" + }, + "oldUIMessage": { + "message": "Vrátili jste se ke starému rozhraní. Můžete přepnout na nové rozhraní v nastavení v pravém horním menu." + }, + "or": { + "message": "nebo", + "description": "choice between creating or importing a new account" + }, + "passwordCorrect": { + "message": "Ujistěte se, že je vaše heslo správně." + }, + "passwordMismatch": { + "message": "hesla nesouhlasí", + "description": "in password creation process, the two new password fields did not match" + }, + "passwordShort": { + "message": "heslo je krátké", + "description": "in password creation process, the password is not long enough to be secure" + }, + "pastePrivateKey": { + "message": "Vložte zde svůj privátní klíč:", + "description": "For importing an account from a private key" + }, + "pasteSeed": { + "message": "Svou klíčovou frázi vložte zde!" + }, + "personalAddressDetected": { + "message": "Detekována osobní adresa. Zadejte adresu kontraktu tokenu." + }, + "pleaseReviewTransaction": { + "message": "Zkontrolujte si transakci." + }, + "popularTokens": { + "message": "Oblíbené tokeny" + }, + "privacyMsg": { + "message": "Zásady ochrany osobních údajů" + }, + "privateKey": { + "message": "Privátní klíč", + "description": "select this type of file to use to import an account" + }, + "privateKeyWarning": { + "message": "Upozornění: Nikdy nezveřejněte tento klíč. Kdokoli může s vaším privátním klíčem odcizit vaše aktiva z účtu." + }, + "privateNetwork": { + "message": "Soukromá síť" + }, + "qrCode": { + "message": "Ukázat QR kód" + }, + "readdToken": { + "message": "Tento token můžete v budoucnu přidat zpět s „Přidat token“ v nastavení účtu." + }, + "readMore": { + "message": "Přečtěte si více zde." + }, + "readMore2": { + "message": "Přečtěte si více." + }, + "receive": { + "message": "Obrdžet" + }, + "recipientAddress": { + "message": "Adresa příjemce" + }, + "refundAddress": { + "message": "Adresa pro vrácení peněz" + }, + "rejected": { + "message": "Odmítnuto" + }, + "resetAccount": { + "message": "Resetovat účet" + }, + "restoreFromSeed": { + "message": "Obnovit z seed fráze" + }, + "restoreVault": { + "message": "Obnovit trezor" + }, + "required": { + "message": "Povinné" + }, + "retryWithMoreGas": { + "message": "Opakujte s vyšší cenou paliva" + }, + "walletSeed": { + "message": "Klíčová fráze peněženky" + }, + "revealSeedWords": { + "message": "Zobrazit slova klíčové fráze" + }, + "revealSeedWordsWarning": { + "message": "Nebnovujte slova klíčové fráze na veřejnosti! Tato slova mohou být použita k odcizení veškerých vyašich účtů." + }, + "revert": { + "message": "Zvrátit" + }, + "rinkeby": { + "message": "Rinkeby Test Network" + }, + "ropsten": { + "message": "Ropsten Test Network" + }, + "currentRpc": { + "message": "Současné RPC" + }, + "connectingToMainnet": { + "message": "Připojuji se k Main Ethereum Network" + }, + "connectingToRopsten": { + "message": "Připojuji se k Ropsten Test Network" + }, + "connectingToKovan": { + "message": "Připojuji se k Kovan Test Network" + }, + "connectingToRinkeby": { + "message": "Připojuji se k Rinkeby Test Network" + }, + "connectingToUnknown": { + "message": "Připojuji se k neznámé síti" + }, + "sampleAccountName": { + "message": "Např. můj nový účet", + "description": "Help user understand concept of adding a human-readable name to their account" + }, + "save": { + "message": "Uložit" + }, + "reprice_title": { + "message": "Změnit cenu transakce" + }, + "reprice_subtitle": { + "message": "Navyšte cenu paliva ve snaze k přepsání a urychlení vyší transakce" + }, + "saveAsFile": { + "message": "Uložit do souboru", + "description": "Account export process" + }, + "saveSeedAsFile": { + "message": "Uložit slova klíčové fráze do souboru" + }, + "search": { + "message": "Hledat" + }, + "secretPhrase": { + "message": "Zadejte svých 12 slov tajné fráze k obnovení trezoru." + }, + "newPassword8Chars": { + "message": "Nové heslo (min 8 znaků)" + }, + "seedPhraseReq": { + "message": "klíčové fráze mají 12 slov" + }, + "select": { + "message": "Vybrat" + }, + "selectCurrency": { + "message": "Vybrat měnu" + }, + "selectService": { + "message": "Vybrat službu" + }, + "selectType": { + "message": "Vybrat typ" + }, + "send": { + "message": "Odeslat" + }, + "sendETH": { + "message": "Odeslat ETH" + }, + "sendTokens": { + "message": "Odeslat tokeny" + }, + "onlySendToEtherAddress": { + "message": "Posílejte jen ETH na Ethereum adresu." + }, + "searchTokens": { + "message": "Hledat tokeny" + }, + "sendTokensAnywhere": { + "message": "Posílejte tokeny komukoli s Ethereum účtem" + }, + "settings": { + "message": "Nastavení" + }, + "info": { + "message": "Informace" + }, + "shapeshiftBuy": { + "message": "Nakoupit na ShapeShift" + }, + "showPrivateKeys": { + "message": "Zobrazit privátní klíče" + }, + "showQRCode": { + "message": "Zobrazit QR kód" + }, + "sign": { + "message": "Podepsat" + }, + "signed": { + "message": "Podepsáno" + }, + "signMessage": { + "message": "Podepsat zprávu" + }, + "signNotice": { + "message": "Podepsání zprávy může mít \nnebezpečný vedlejší učinek. Podepisujte zprávy pouze ze \nstránek, kterým plně důvěřujete celým svým účtem.\n Tato nebezpečná metoda bude odebrána v budoucí verzi. " + }, + "sigRequest": { + "message": "Požadavek podpisu" + }, + "sigRequested": { + "message": "Požádáno o podpis" + }, + "spaceBetween": { + "message": "mezi slovy může být pouze mezera" + }, + "status": { + "message": "Stav" + }, + "stateLogs": { + "message": "Stavové protokoly" + }, + "stateLogsDescription": { + "message": "Stavové protokoly obsahují vaše veřejné adresy účtů a odeslané transakce." + }, + "stateLogError": { + "message": "Chyba během získávání stavových protokolů." + }, + "submit": { + "message": "Odeslat" + }, + "submitted": { + "message": "Odesláno" + }, + "supportCenter": { + "message": "Navštivte naše centrum podpory" + }, + "symbolBetweenZeroTen": { + "message": "Symbol musí být mezi 0 a 10 znaky." + }, + "takesTooLong": { + "message": "Trvá to dlouho?" + }, + "terms": { + "message": "Podmínky použití" + }, + "testFaucet": { + "message": "Testovací faucet" + }, + "to": { + "message": "Komu" + }, + "toETHviaShapeShift": { + "message": "$1 na ETH přes ShapeShift", + "description": "system will fill in deposit type in start of message" + }, + "tokenAddress": { + "message": "Adresa tokenu" + }, + "tokenAlreadyAdded": { + "message": "Token byl už přidán." + }, + "tokenBalance": { + "message": "Váš zůstatek tokenu je:" + }, + "tokenSelection": { + "message": "Vyhledejte token nebo je vyberte z našeho seznamu oblíbených tokenů." + }, + "tokenSymbol": { + "message": "Symbol tokenu" + }, + "tokenWarning1": { + "message": "Mějte přehled o tokenech, které jste koupili s účtem MetaMasku. Pokud jste koupili tokeny s jiným účtem, tyto tokeny se zde nezobrazí." + }, + "total": { + "message": "Celkem" + }, + "transactions": { + "message": "transakce" + }, + "transactionError": { + "message": "Chyba transakce. Vyhozena výjimka v kódu kontraktu." + }, + "transactionMemo": { + "message": "Poznámka transakce (nepovinné)" + }, + "transactionNumber": { + "message": "Číslo transakce" + }, + "transfers": { + "message": "Převody" + }, + "troubleTokenBalances": { + "message": "Měli jsme problém s načtením vašich tokenových zůstatků. Můžete je vidět ", + "description": "Followed by a link (here) to view token balances" + }, + "twelveWords": { + "message": "Těchto 12 slov je jedinou možností, jak obnovit MetaMask účet. \nUložte je na bezpečné a neveřejné místo." + }, + "typePassword": { + "message": "Zadejte své heslo" + }, + "uiWelcome": { + "message": "Vítejte v novém rozhraní (Beta)" + }, + "uiWelcomeMessage": { + "message": "Používáte nyní nové rozhraní MetaMasku. Rozhlédněte se kolem, vyzkoušejte nové funkce, jako jsou zasílání tokenů, a dejte nám vědět, pokud narazíte na problém." + }, + "unapproved": { + "message": "Neschváleno" + }, + "unavailable": { + "message": "Nedostupné" + }, + "unknown": { + "message": "Neznámé" + }, + "unknownNetwork": { + "message": "Neznámá soukromá síť" + }, + "unknownNetworkId": { + "message": "Neznámé ID sítě" + }, + "uriErrorMsg": { + "message": "URI vyžadují korektní HTTP/HTTPS prefix." + }, + "usaOnly": { + "message": "jen v USA", + "description": "Using this exchange is limited to people inside the USA" + }, + "usedByClients": { + "message": "Používána různými klienty" + }, + "useOldUI": { + "message": "Použijte staré rozhraní" + }, + "validFileImport": { + "message": "Musíte vybrat validní soubor k importu." + }, + "vaultCreated": { + "message": "Trezor vytvořen" + }, + "viewAccount": { + "message": "Zobrazit účet" + }, + "visitWebSite": { + "message": "Navštivte naši stránku" + }, + "warning": { + "message": "Varování" + }, + "welcomeBeta": { + "message": "Vítejte v MetaMask Beta" + }, + "whatsThis": { + "message": "Co to je?" + }, + "yourSigRequested": { + "message": "Je vyžadován váš podpis" + }, + "youSign": { + "message": "Podepisujete" + } +} diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json index b9c4ff8e0..ee9f67b97 100644 --- a/app/_locales/sl/messages.json +++ b/app/_locales/sl/messages.json @@ -1,957 +1,1372 @@ { "privacyMode": { - "message": "Režim súkromia" + "message": "Zasebnostni način" }, "privacyModeDescription": { - "message": "Webové stránky musia požiadať o prístup k zobrazeniu informácií o vašom účte." + "message": "Spletne strani morajo zahtevati dovoljenje za ogled podatkov o vašem računu." }, "exposeAccounts": { - "message": "Vystavte účty" + "message": "Razkrij račune" }, "exposeDescription": { - "message": "Vystavte účty na aktuální webové stránky. Užitečné pro starší dappy." + "message": "Razkrij račune trenutni spletni strani. Priporočeno za starejše dApps." }, "confirmExpose": { - "message": "Opravdu chcete své účty vystavit na stávajícím webu?" + "message": "Ste prepričani da želite razkriti račune trenutni spletni strani?" }, "confirmClear": { - "message": "Naozaj chcete vymazať schválené webové stránky?" + "message": "Ste prepričani da želite počistiti odobrene spletne strani?" }, "clearApprovalDataSuccess": { - "message": "Schválené údaje webových stránek byly úspěšně zrušeny." + "message": "Odobrene spletne strani uspešno počiščene." }, "approvalData": { - "message": "Údaje o schválení" + "message": "Podatki o odobritvi" }, "approvalDataDescription": { - "message": "Vymažte schválené údaje webových stránek, aby všechny weby znovu požádaly o schválení." + "message": "Počistite seznam odobrenih spletnih strani, tako da bodo morale ponovno zahtevati odobritev." }, "clearApprovalData": { - "message": "Jasné údaje o schválení" - }, - "approve": { - "message": "Schválit" - }, - "reject": { - "message": "Odmítnout" + "message": "Počisti podatke o odobritvi" }, "providerAPIRequest": { - "message": "Požadavek API Ethereum" + "message": "Zahteva za Ethereum API" }, "reviewProviderRequest": { - "message": "Přečtěte si prosím tuto žádost API Ethereum." + "message": "Preglejte zahtevo za Ethereum API." }, "providerRequestInfo": { - "message": "Níže uvedená doména se pokouší požádat o přístup k API Ethereum, aby mohla komunikovat s blokádou Ethereum. Před schválením přístupu Ethereum vždy zkontrolujte, zda jste na správném místě." + "message": "Domena zahteva dostop do verige blokov in ogled vašega računa. Pred potrditvjo vedno preverite ali ste na želeni spletni strani." }, "accept": { - "message": "Přijmout" + "message": "Potrdi" + }, + "accessingYourCamera": { + "message": "Dostopanje do vaše kamere ..." }, "account": { - "message": "Účet" + "message": "Račun" }, "accountDetails": { - "message": "Detaily účtu" + "message": "Podrobnosti računa" }, "accountName": { - "message": "Název účtu" + "message": "Ime računa" + }, + "accountOptions": { + "message": "Možnosti računa" + }, + "accountSelectionRequired": { + "message": "Izbrati morate račun!" + }, + "activityLog": { + "message": "dnevnik dejavnosti" }, "address": { - "message": "Adresa" + "message": "Naslov" }, "addCustomToken": { - "message": "Přidat vlastní token" + "message": "Dodaj žeton po meri" }, "addToken": { - "message": "Přidat token" + "message": "Dodaj žeton" }, "addTokens": { - "message": "Přidat tokeny" + "message": "Dodaj žetone" + }, + "addSuggestedTokens": { + "message": "Dodaj priporočene žetone" + }, + "addAcquiredTokens": { + "message": "Dodaj žetone pridobljene z MetaMask" }, "amount": { - "message": "Částka" + "message": "Znesek" }, "amountPlusGas": { - "message": "Částka + palivo" + "message": "Znesek + Gas" }, "appDescription": { - "message": "Ethereum rozšíření prohlížeče", + "message": "Denarnica za Ethereum v brskalniku", "description": "The description of the application" }, "appName": { "message": "MetaMask", "description": "The name of the application" }, + "approve": { + "message": "Potrdi" + }, "approved": { - "message": "Schváleno" + "message": "Potrjeno" }, "attemptingConnect": { - "message": "Pokouším se připojit k blockchainu." + "message": "Povezovanje z verigo blokov ..." + }, + "attemptToCancel": { + "message": "Želite preklicati?" + }, + "attemptToCancelDescription": { + "message": "Če pošljete ta poskus, ne boste zagotovili, da bo vaša prvotna transakcija preklicana. Če bo poskus preklica uspešen, se bo zaračunala zgornja transakcija." }, "attributions": { - "message": "Zásluhy" + "message": "Dodelitve" }, "available": { - "message": "Dostupné" + "message": "Na voljo" }, "back": { - "message": "Zpět" + "message": "Nazaj" }, "balance": { - "message": "Zůstatek:" + "message": "Znesek" }, "balances": { - "message": "Zůstatek tokenu" + "message": "Znesek žetonov" }, "balanceIsInsufficientGas": { - "message": "Nedostatek prostředků pro aktuální množství paliva" + "message": "Nezadnosten znesek za trenutni gas" }, "beta": { "message": "BETA" }, "betweenMinAndMax": { - "message": "musí být větší nebo roven $1 a menší nebo roven $2.", + "message": "mora biti večji ali enak $1 in manjši ali enak $1.", "description": "helper for inputting hex as decimal input" }, "blockiesIdenticon": { - "message": "Použít Blockies Identicon" + "message": "Uporabi identifikacijo Blockies" }, "borrowDharma": { - "message": "Pújčit si přes Dharma (Beta)" + "message": "Izposoja z Dharma (Beta)" + }, + "browserNotSupported": { + "message": "Vaš brskalnik ni podptrt ..." }, "builtInCalifornia": { - "message": "MetaMask je navržen a vytvořen v Kalifornii." + "message": "MetaMask je zasnovan in ustvarjen v Kaliforniji." }, "buy": { - "message": "Koupit" + "message": "Kupi" }, "buyCoinbase": { - "message": "Nákup na Coinbase" + "message": "Kupi na Coinbase" }, "buyCoinbaseExplainer": { - "message": "Coinbase je světově nejoblíbenější místo k nákupu a prodeji bitcoinu, etherea nebo litecoinu." + "message": "Coinbase je najpopularnejši načun za kupovanje in prodajo bitcoinov, ethereuma, in litecoina." + }, + "bytes": { + "message": "Bajti" }, "ok": { - "message": "Ok" + "message": "V redu" }, "cancel": { - "message": "Zrušit" + "message": "Prekliči" + }, + "cancelAttempt": { + "message": "Prekliči poskus" + }, + "cancellationGasFee": { + "message": "Preklicani znesek gas" + }, + "cancelN": { + "message": "Prekliči vseh $1 transakcij" }, "classicInterface": { - "message": "Použít klasické rozhraní" + "message": "Uporabi klasični vmesnik" }, "clickCopy": { - "message": "Kliknutím zkopírovat" + "message": "Kliknite za kopiranje" + }, + "clickToAdd": { + "message": "Kliknite na $1 za dodajo v vaš račun" + }, + "close": { + "message": "Zapri" + }, + "chromeRequiredForHardwareWallets": { + "message": "Za uporabo strojne denarnice potrebujete Google Chrome." }, "confirm": { - "message": "Potvrdit" + "message": "Potrdi" }, "confirmed": { - "message": "Potvrzeno" + "message": "Potrjeno" }, "confirmContract": { - "message": "Potvrdit kontrakt" + "message": "Potrdi pogodbo" }, "confirmPassword": { - "message": "Potvrdit heslo" + "message": "Potrdi geslo" }, "confirmTransaction": { - "message": "Potvrdit transakci" + "message": "Potrdi transakcijo" + }, + "connectHardwareWallet": { + "message": "Poveži strojno denarnico" + }, + "connect": { + "message": "Poveži" + }, + "connecting": { + "message": "Povezovanje ..." + }, + "connectingToKovan": { + "message": "Povezovanje na testno omrežje Kovan" + }, + "connectingToMainnet": { + "message": "Povezovanje na glavno omrežje" + }, + "connectingToRopsten": { + "message": "Povezovanje na testno omrežje Ropsten" + }, + "connectingToRinkeby": { + "message": "Povezovanje na testno omrežje Rinkeby" + }, + "connectingToUnknown": { + "message": "Povezovanje na neznano omrežje" + }, + "connectToLedger": { + "message": "Poveži z Ledger" + }, + "connectToTrezor": { + "message": "Poveži z Trezor" }, "continue": { - "message": "Pokračovat" + "message": "Nadaljuj" }, "continueToCoinbase": { - "message": "Přejít na Coinbase" + "message": "Nadaljuj na Coinbase" }, "contractDeployment": { - "message": "Nasazení kontraktu" + "message": "Ustvarjanje pogodbe" }, "conversionProgress": { - "message": "Provádí se převod" + "message": "Pretvorba v teku" }, "copiedButton": { - "message": "Zkopírováno" + "message": "Kopirano" }, "copiedClipboard": { - "message": "Zkopírováno do schránky" + "message": "Kopirano v odložišče!" }, "copiedExclamation": { - "message": "Zkopírováno!" + "message": "Kopirano!" }, "copiedSafe": { - "message": "Zkopíroval jsem to na bezpečné místo" + "message": "Prilepil sem ga na varno" }, "copy": { - "message": "Kopírovat" + "message": "Kopiraj" + }, + "copyAddress": { + "message": "Kopiraj naslov v odložišče" }, "copyToClipboard": { - "message": "Kopírovat do schránky" + "message": "Kopiraj v odložišče" }, "copyButton": { - "message": " Kopírovat " + "message": " Kopiraj " }, "copyPrivateKey": { - "message": "Toto je váš privátní klíč (kliknutím zkopírujte)" + "message": "To je vaš zesebni ključ (kliknite za kopiranje)" }, "create": { - "message": "Vytvořit" + "message": "Ustvari" }, "createAccount": { - "message": "Vytvořit účet" + "message": "Ustvari račun" }, "createDen": { - "message": "Vytvořit" + "message": "Ustvari" }, "crypto": { - "message": "Krypto", + "message": "Kripto", "description": "Exchange type (cryptocurrencies)" }, "currentConversion": { - "message": "Aktuální převod" + "message": "Trenutna pretvorba" + }, + "currentLanguage": { + "message": "Trenutni jezik" }, "currentNetwork": { - "message": "Aktuální síť" + "message": "Trenutno omrežje" + }, + "currentRpc": { + "message": "Trenutni RPC" }, "customGas": { - "message": "Nastavit palivo" + "message": "Prilagodi gas" }, "customToken": { - "message": "Vlastní token" + "message": "Žeton po meri" }, "customize": { - "message": "Nastavit" + "message": "Prilagodi" }, "customRPC": { - "message": "Vlastní RPC" + "message": "RPC po meri" }, "decimalsMustZerotoTen": { - "message": "Desetinných míst musí být od 0 do 36." + "message": "Decimalk mora biti med 0 in 36." }, "decimal": { - "message": "Počet desetinných míst přesnosti" + "message": "Decimalna natančnost" }, "defaultNetwork": { - "message": "Výchozí síť pro Etherové transakce je Main Net." + "message": "Privzeto omrežje za transkacije je glavno omrežje." }, "denExplainer": { - "message": "Váš DEN je heslem šifrované uložiště v MetaMasku." + "message": "Vaš DEN je z geslom šifrirana shramba v MetaMask." }, "deposit": { - "message": "Vklad" + "message": "Vplačaj" }, "depositBTC": { - "message": "Vložte BTC na níže uvedenou adresu:" + "message": "Vplačajte vaš BTC na spodnji naslov:" }, "depositCoin": { - "message": "Vložte $1 na níže uvedenou adresu", + "message": "Vplačajte vaš $1 na spodnji naslov", "description": "Tells the user what coin they have selected to deposit with shapeshift" }, "depositEth": { - "message": "Vložit Eth" + "message": "Vplačilo ETH" }, "depositEther": { - "message": "Vložit Ether" + "message": "Vplačilo ethra" }, "depositFiat": { - "message": "Vklad s fiat měnou" + "message": "Vplačilo s klasičnimi valutami" }, "depositFromAccount": { - "message": "Vložte z jiného účtu" + "message": "Vplačilo iz drugega računa" }, "depositShapeShift": { - "message": "Vklad přes ShapeShift" + "message": "Vplačilo z ShapeShift" }, "depositShapeShiftExplainer": { - "message": "Pokud vlastníte jiné kryptoměny, můžete je směnit Ether a vložit ho přímo do peněženky MetaMask. Bez založení účtu." + "message": "Če imate druge kriptovalute, lahko Ether vplačate neposredno v MetaMask. Brez računov." }, "details": { "message": "Podrobnosti" }, "directDeposit": { - "message": "Přímý vklad" + "message": "Neposredno vplačilo" }, "directDepositEther": { - "message": "Vložit Ether přímo" + "message": "Neposredno vplačilo ehera" }, "directDepositEtherExplainer": { - "message": "Pokud už vlastníte nějaký Ether, nejrychleji ho dostanete do peněženky přímým vkladem." + "message": "Če že imate Ether, ga lahko najhitreje dobite v MetaMask z neposrednim vplačilom." }, "done": { - "message": "Hotovo" + "message": "Končano" + }, + "downloadGoogleChrome": { + "message": "Prenesi Google Chrome" }, "downloadStateLogs": { - "message": "Stáhnout stavové protokoly" + "message": "Prenesi dnevnike state" + }, + "dontHaveAHardwareWallet": { + "message": "Nimate strojne denarnice?" }, "dropped": { - "message": "Zrušeno" + "message": "Izpusti" }, "edit": { - "message": "Upravit" + "message": "Uredi" }, "editAccountName": { - "message": "Upravit název účtu" + "message": "Uredi ime računa" + }, + "editingTransaction": { + "message": "Uredite transakcijo" }, "emailUs": { - "message": "Napište nám e-mail!" + "message": "Pišite nam!" }, "encryptNewDen": { - "message": "Zašifrujte svůj nový DEN" + "message": "Šifrirajte vaš DEN" + }, + "ensNameNotFound": { + "message": "Ime ENS ni najdeno" }, "enterPassword": { - "message": "Zadejte heslo" + "message": "Vnesite geslo" }, "enterPasswordConfirm": { - "message": "Zadejte heslo k potvrzení" + "message": "Potrdite geslo" }, - "passwordNotLongEnough": { - "message": "Heslo není dost dlouhé" + "enterPasswordContinue": { + "message": "Za nadaljevanje vnesite geslo" }, - "passwordsDontMatch": { - "message": "Hesla nejsou stejná" + "eth": { + "message": "ETH" }, "etherscanView": { - "message": "Prohlédněte si účet na Etherscan" + "message": "Poglej račun na Etherscan" }, "exchangeRate": { - "message": "Směnný kurz" + "message": "Menjalni tečaj" + }, + "expandView": { + "message": "Razširi pogled" }, "exportPrivateKey": { - "message": "Exportovat privátní klíč" + "message": "Izvozi zasebni ključ" }, "exportPrivateKeyWarning": { - "message": "Exportujte privátní klíč na vlastní riziko." + "message": "Izvoz zasebnega ključa na lastno odgovornost." }, "failed": { - "message": "Neúspěšné" + "message": "Ni uspelo" }, "fiat": { - "message": "FIAT", + "message": "Klasične", "description": "Exchange type" }, "fileImportFail": { - "message": "Import souboru nefunguje? Klikněte sem!", + "message": "Uvoz z datoteko ne deluje? Kliknite tukaj!", "description": "Helps user import their account from a JSON file" }, "followTwitter": { - "message": "Sledujte nás na Twitteru" + "message": "Sledite nam na Twitterju" + }, + "forgetDevice": { + "message": "Pozabi to napravo" }, "from": { "message": "Od" }, "fromToSame": { - "message": "Adresy odesílatele a příjemce nemohou být stejné" + "message": "Naslova pošiljatelja in prejemnika ne smeta biti enaka" }, "fromShapeShift": { - "message": "Z ShapeShift" + "message": "Od ShapeShift" + }, + "functionType": { + "message": "Vrsta funkcije" }, "gas": { - "message": "Palivo", + "message": "Gas", "description": "Short indication of gas cost" }, "gasFee": { - "message": "Poplatek za palivo" + "message": "Gas Fee" }, "gasLimit": { - "message": "Limit paliva" + "message": "Gas Limit" }, "gasLimitCalculation": { - "message": "Počítáme doporučený limit paliva na základě úspěšnosti v síti." + "message": "Priporočen gas limit je izračunan glede na omrežje." }, "gasLimitRequired": { - "message": "Limit paliva je povinný" + "message": "Gas limit je zahtevan" }, "gasLimitTooLow": { - "message": "Limit paliva musí být alespoň 21000" + "message": "Gas limit mora biti vsaj 21000" + }, + "gasUsed": { + "message": "Uporabljen gas" }, "generatingSeed": { - "message": "Generuji klíčovou frázi..." + "message": "Ustvarjenje seed ..." }, "gasPrice": { - "message": "Cena paliva (GWEI)" + "message": "Gas Price (GWEI)" }, "gasPriceCalculation": { - "message": "Počítáme doporučenou cenu paliva na základě úspěšnosti v síti." + "message": "Priporočen gas price je izračunan glede na omrežje." }, "gasPriceRequired": { - "message": "Cena paliva je povinná" + "message": "Gas price je zahtevan" + }, + "generatingTransaction": { + "message": "Ustvarjanje transakcije" }, "getEther": { - "message": "Získejte Ether" + "message": "Pridobi Ether" }, "getEtherFromFaucet": { - "message": "Získejte Ether z faucetu za $1.", + "message": "Pridobite Ether iz fauceta za $1", "description": "Displays network name for Ether faucet" }, + "getHelp": { + "message": "Get Help." + }, "greaterThanMin": { - "message": "musí být větší nebo roven $1.", + "message": "mora biti večji ali enak $1.", "description": "helper for inputting hex as decimal input" }, + "hardware": { + "message": "strojna" + }, + "hardwareWalletConnected": { + "message": "Strojna denarnica povezana" + }, + "hardwareWallets": { + "message": "Poveži strojno denarnico" + }, + "hardwareWalletsMsg": { + "message": "Izberite vrsto strojne denarnice" + }, + "havingTroubleConnecting": { + "message": "Imate težave?" + }, "here": { - "message": "zde", + "message": "tukaj", "description": "as in -click here- for more information (goes with troubleTokenBalances)" }, "hereList": { - "message": "Tady je seznam!!!!" + "message": "Tukaj je seznam!!!!" + }, + "hexData": { + "message": "Hex Data" }, "hide": { - "message": "Skrýt" + "message": "Skrij" }, "hideToken": { - "message": "Skrýt token" + "message": "Skrij žeton" }, "hideTokenPrompt": { - "message": "Skrýt token?" + "message": "Skrijem žeton?" + }, + "history": { + "message": "Zgodovina" }, "howToDeposit": { - "message": "Jakým způsobem chcete vložit Ether?" + "message": "Kako želite vplačati Ether?" }, "holdEther": { - "message": "Dovoluje vám držet ether a tokeny a slouží jako most k decentralizovaným aplikacím." + "message": "Omogoča vam shranjevanje ethra in žetonov ter povezovanje decentraliziranih aplikacij." }, "import": { - "message": "Import", + "message": "Uvoz", "description": "Button to import an account from a selected file" }, "importAccount": { - "message": "Import účtu" + "message": "Uvoz računa" }, "importAccountMsg": { - "message": "Importované účty nebudou spojeny s vaší původní MetaMaskovou klíčovou frází. Zjistěte více o importovaných účtech " + "message": " Uvoženi računa ne bodo povezani s prvotnim seed phase. Preberite več o uvoženih računih " }, "importAnAccount": { - "message": "Import účtu" + "message": "IUvozi račun" }, "importDen": { - "message": "Import existujícího DEN" + "message": "Uvozi DEN" }, "imported": { - "message": "Importováno", + "message": "Uvoženo", "description": "status showing that an account has been fully loaded into the keyring" }, + "importUsingSeed": { + "message": "Uvozi z seed phase" + }, + "info": { + "message": "Info" + }, "infoHelp": { - "message": "Informace a nápověda" + "message": "Info & Pomoč" + }, + "initialTransactionConfirmed": { + "message": "Vaša prvotna transakcija je bila potrjena. Pritisnite V redu in se vrnite nazaj." }, "insufficientFunds": { - "message": "Nedostatek finančních prostředků." + "message": "Nezadostna sredstva." }, "insufficientTokens": { - "message": "Nedostatek tokenů." + "message": "Nezadostni žetoni." }, "invalidAddress": { - "message": "Neplatná adresa" + "message": "Neveljaven naslov" }, "invalidAddressRecipient": { - "message": "Adresa příjemce je neplatná" + "message": "Neveljaven nasklov prejemnika" }, "invalidGasParams": { - "message": "Neplatná parametry paliva" + "message": "Neveljavne gas nastavitve" }, "invalidInput": { - "message": "Neplatný vstup." + "message": "Neveljaven vnos." }, "invalidRequest": { - "message": "Neplatný požadavek" + "message": "Neveljavna zahteva" }, "invalidRPC": { - "message": "Neplatné RPC URI" + "message": "Neveljaven RPC URL" + }, + "invalidSeedPhrase": { + "message": "Neveljavna seed phrase" }, "jsonFail": { - "message": "Něco se pokazilo. Prosím, ujistěte se, že váš JSON soubor má správný formát." + "message": "Nekaj je bilo narobe. Prepričajte se, da je datoteka JSON pravilno oblikovana." }, "jsonFile": { - "message": "JSON soubor", + "message": "Datoteka JSON", "description": "format for importing an account" }, "keepTrackTokens": { - "message": "Udržujte si záznamy o tokenech, které jste koupili s účtem v MetaMasku." + "message": "Sledite žetonom, ki ste jih pridobili z MetaMask." }, "kovan": { - "message": "Kovan Test Network" + "message": "Testno omrežje Kovan" }, "knowledgeDataBase": { - "message": "Navštivte naši Knowledge Base" + "message": "Obiščite našo pomoč" }, "max": { "message": "Max" }, "learnMore": { - "message": "Zjistěte více." + "message": "Preberite več" + }, + "ledgerAccountRestriction": { + "message": "Za dodajanje novega računa morate uporabiti zadnji račun." }, "lessThanMax": { - "message": "musí být menší nebo roven $1.", + "message": "mora biti manjši ali enak $1.", "description": "helper for inputting hex as decimal input" }, "likeToAddTokens": { - "message": "Chcete přidat tyto tokeny?" + "message": "Želite dodati te žetone?" }, "links": { - "message": "Odkazy" + "message": "Povezave" }, "limit": { - "message": "Limit" + "message": "Meja" }, "loading": { - "message": "Načítám..." + "message": "Nalaganje ..." }, "loadingTokens": { - "message": "Načítám tokeny..." + "message": "Nalaganje žetonov ..." }, "localhost": { "message": "Localhost 8545" }, "login": { - "message": "Přihlásit" + "message": "Prijava" }, "logout": { - "message": "Odhlásit" + "message": "Odjava" }, "loose": { - "message": "Nevázané" + "message": "Loose" }, "loweCaseWords": { - "message": "slova klíčové fráze mají pouze malá písmena" + "message": "seed words lahko vsebujejo samo male črke" }, "mainnet": { - "message": "Main Ethereum Network" + "message": "Glavno omrežje" + }, + "menu": { + "message": "Meni" }, "message": { - "message": "Zpráva" + "message": "Sporočilo" }, "metamaskDescription": { - "message": "MetaMask je bezpečný osobní trezor pro Ethereum." + "message": "MetaMask je varen identitetni sklad za Ethereum." + }, + "metamaskSeedWords": { + "message": "Seed Words" + }, + "metamaskVersion": { + "message": "Različica" }, "min": { - "message": "Minimum" + "message": "Najmanj" + }, + "missingYourTokens": { + "message": "Ne vidite vaših žetonov?" }, "myAccounts": { - "message": "Moje účty" + "message": "Moji računi" }, "mustSelectOne": { - "message": "Musíte zvolit aspoň 1 token." + "message": "Izbran mora biti vsaj 1 žeton." }, "needEtherInWallet": { - "message": "Potřebujete Ether v peněžence, abyste mohli pomocí MetaMasku interagovat s decentralizovanými aplikacemi." + "message": "Za interakcijo z decentraliziranimi aplikacijami boste v svoji denarnici potrebovali Eter." }, "needImportFile": { - "message": "Musíte zvolit soubor k importu.", + "message": "Za uvoz morate izbrati datoteko.", "description": "User is important an account and needs to add a file to continue" }, "needImportPassword": { - "message": "Musíte zadat heslo pro zvolený soubor.", + "message": "Za izbrano datoteko morate vnesti geslo.", "description": "Password and file needed to import an account" }, "negativeETH": { - "message": "Nelze odeslat zápornou částku ETH." + "message": "Negativnih zneskov ETH ni mogoče poslati." }, "networks": { - "message": "Sítě" + "message": "Omrežja" + }, + "nevermind": { + "message": "Pozabi" }, "newAccount": { - "message": "Nový účet" + "message": "Nov račun" }, "newAccountNumberName": { - "message": "Účet $1", + "message": "Račun $1", "description": "Default name of next account to be created on create account screen" }, "newContract": { - "message": "Nový kontrakt" + "message": "Nova pogodba" }, "newPassword": { - "message": "Nové heslo (min 8 znaků)" + "message": "Novo geslo (min 8 znakov)" + }, + "newPassword8Chars": { + "message": "Novo geslo (min 8 znakov)" }, "newRecipient": { - "message": "Nový příjemce" + "message": "Nov prejemnik" + }, + "newNetwork": { + "message": "Novo omrežje" + }, + "rpcURL": { + "message": "Nov RPC URL" }, - "newRPC": { - "message": "Nová RPC URL" + "showAdvancedOptions": { + "message": "Pokaži napredne možnosti" + }, + "hideAdvancedOptions": { + "message": "Skrij napredne možnosti" + }, + "optionalChainId": { + "message": "ChainID (nezahtevano)" + }, + "optionalSymbol": { + "message": "Simbol (nezahtevano)" + }, + "optionalNickname": { + "message": "Uporabniško ime (nezahtevano)" }, "next": { - "message": "Další" + "message": "Naprej" }, "noAddressForName": { - "message": "Pro toto jméno nebyla nastavena žádná adresa." + "message": "Za to ime ni naslovov." }, "noDeposits": { - "message": "Žádný vklad" + "message": "Prejetih ni nič vplačil" + }, + "noConversionRateAvailable": { + "message": "Menjalni tečaj ni na voljo" }, "noTransactionHistory": { - "message": "Žádná historie transakcí." + "message": "Ni zgodovine transakcij." }, "noTransactions": { - "message": "Žádné transakce" + "message": "Nimate transakcij" + }, + "notFound": { + "message": "Ni najdeno" }, "notStarted": { - "message": "Nezačalo" + "message": "Ni začeto" + }, + "noWebcamFoundTitle": { + "message": "Spletna kamera ni najdena" + }, + "noWebcamFound": { + "message": "Spletna kamera ni najdena. Poskusite znova kasneje." }, "oldUI": { - "message": "Staré rozhraní" + "message": "Star UI" }, "oldUIMessage": { - "message": "Vrátili jste se ke starému rozhraní. Můžete přepnout na nové rozhraní v nastavení v pravém horním menu." + "message": "Vrnili ste se v star uporabniški vmesnik. V novega se lahko vrnete z možnostjo v spustnem meniju v zgornjem desnem kotu." + }, + "onlySendToEtherAddress": { + "message": "Na Ethereum naslov pošljute samo ETH." + }, + "onlySendTokensToAccountAddress": { + "message": "Na Ethereum naslov pošljute samo $1.", + "description": "displays token symbol" + }, + "openInTab": { + "message": "Odpri v zavihku" }, "or": { - "message": "nebo", + "message": "ali", "description": "choice between creating or importing a new account" }, + "orderOneHere": { + "message": "Naročite Trezor ali Ledger i n zavarujte svoje premoženje s strojno denarnico" + }, + "origin": { + "message": "Izvor" + }, + "outgoing": { + "message": "Odhodni" + }, + "parameters": { + "message": "Parametri" + }, + "password": { + "message": "Geslo" + }, "passwordCorrect": { - "message": "Ujistěte se, že je vaše heslo správně." + "message": "Prepričajte se, da je geslo pravilno." + }, + "passwordsDontMatch": { + "message": "Gesli se ne ujemata" }, "passwordMismatch": { - "message": "hesla nesouhlasí", + "message": "gesli se ne ujemata", "description": "in password creation process, the two new password fields did not match" }, + "passwordNotLongEnough": { + "message": "Geslo ni dovolj dolgo" + }, "passwordShort": { - "message": "heslo je krátké", + "message": "geslo ni dovolj dolgo", "description": "in password creation process, the password is not long enough to be secure" }, "pastePrivateKey": { - "message": "Vložte zde svůj privátní klíč:", + "message": "Tukaj prilepite vaš zasebni ključ:", "description": "For importing an account from a private key" }, "pasteSeed": { - "message": "Svou klíčovou frázi vložte zde!" + "message": "Tukaj prilepite seed phase!" + }, + "pending": { + "message": "v obdelavi" }, "personalAddressDetected": { - "message": "Detekována osobní adresa. Zadejte adresu kontraktu tokenu." + "message": "Zaznan je osebni naslov. Vnesite naslov pogodbe žetona." }, "pleaseReviewTransaction": { - "message": "Zkontrolujte si transakci." + "message": "Preglejte vašo transakcijo." }, "popularTokens": { - "message": "Oblíbené tokeny" + "message": "Priljubljeni žetoni" + }, + "prev": { + "message": "Prej" + }, + "primaryCurrencySetting": { + "message": "Glavna valuta" + }, + "primaryCurrencySettingDescription": { + "message": "Izberite Native za prikaz vrednosti v privzeti valuti verige (npr. ETH). Izberite Klasične za prikaz vrednosti v izbrani klasični valuti." }, "privacyMsg": { - "message": "Zásady ochrany osobních údajů" + "message": "Zasebnost" }, "privateKey": { - "message": "Privátní klíč", + "message": "Zasebni lljuč", "description": "select this type of file to use to import an account" }, "privateKeyWarning": { - "message": "Upozornění: Nikdy nezveřejněte tento klíč. Kdokoli může s vaším privátním klíčem odcizit vaše aktiva z účtu." + "message": "Opozorilo: Nikoli ne razkrijte tega ključa. Kdorkoli lahko z njim ukrade vse vaše premoženje v računu." }, "privateNetwork": { - "message": "Soukromá síť" + "message": "Zasebno omrežje" }, "qrCode": { - "message": "Ukázat QR kód" + "message": "Prikaži QR kodo" + }, + "queue": { + "message": "Čakalna vrsta" }, "readdToken": { - "message": "Tento token můžete v budoucnu přidat zpět s „Přidat token“ v nastavení účtu." + "message": "Ta žeton lahko dodate tudi kasneje z uporabo gumba “Dodaj žeton” v možnostih vašega računa." }, "readMore": { - "message": "Přečtěte si více zde." + "message": "Tukaj preberite več." }, "readMore2": { - "message": "Přečtěte si více." + "message": "Preberite več." }, "receive": { - "message": "Obrdžet" + "message": "Prejmi" }, "recipientAddress": { - "message": "Adresa příjemce" + "message": "Prejemnikov naslov" }, "refundAddress": { - "message": "Adresa pro vrácení peněz" + "message": "Naslov za vračilo." + }, + "reject": { + "message": "Zavrni" + }, + "rejectAll": { + "message": "Zavrni vse" + }, + "rejectTxsN": { + "message": "Zavrni $1 transakcij" + }, + "rejectTxsDescription": { + "message": "Zavrnili boste $1 transakcij." }, "rejected": { - "message": "Odmítnuto" + "message": "Zavrnjeno" + }, + "reset": { + "message": "Ponastavi" }, "resetAccount": { - "message": "Resetovat účet" + "message": "Ponastavi račun" + }, + "resetAccountDescription": { + "message": "Ponastavljanje računa bo izbrisalo zgodovino transakcij." }, "restoreFromSeed": { - "message": "Obnovit z seed fráze" + "message": "Obnovim račun?" }, "restoreVault": { - "message": "Obnovit trezor" + "message": "Obnovi shrambo" + }, + "restoreAccountWithSeed": { + "message": "Obnovi račun z seed phrase" }, "required": { - "message": "Povinné" + "message": "Zahtevano" }, "retryWithMoreGas": { - "message": "Opakujte s vyšší cenou paliva" + "message": "Poskusite z večjim gas price" }, - "walletSeed": { - "message": "Klíčová fráze peněženky" + "restore": { + "message": "Obnovi" }, "revealSeedWords": { - "message": "Zobrazit slova klíčové fráze" + "message": "Razkrij seed words" + }, + "revealSeedWordsTitle": { + "message": "Seed Phrase" + }, + "revealSeedWordsDescription": { + "message": "Če želite zamenjati brskalnik ali računalnik, potrebujete vaš seed phase za dostop do računa. Shranite ga na varno skrito mesto." + }, + "revealSeedWordsWarningTitle": { + "message": "Tega ne delite z nikomer!" }, "revealSeedWordsWarning": { - "message": "Nebnovujte slova klíčové fráze na veřejnosti! Tato slova mohou být použita k odcizení veškerých vyašich účtů." + "message": "S temi besedami lahko kdorkoli ukrade vse vaše račune." }, "revert": { - "message": "Zvrátit" - }, - "rinkeby": { - "message": "Rinkeby Test Network" + "message": "Obnovi" }, - "ropsten": { - "message": "Ropsten Test Network" + "remove": { + "message": "izbriši" }, - "currentRpc": { - "message": "Současné RPC" + "removeAccount": { + "message": "Izbriši račun" }, - "connectingToMainnet": { - "message": "Připojuji se k Main Ethereum Network" + "removeAccountDescription": { + "message": "Ta račun bo izbrisan. Prepričajte se, da imate seed phase ali zasebni ključ tega računa, da ga boste lahko kasneje obnovili. " }, - "connectingToRopsten": { - "message": "Připojuji se k Ropsten Test Network" + "readyToConnect": { + "message": "Pripravljeni na povezovanje?" }, - "connectingToKovan": { - "message": "Připojuji se k Kovan Test Network" + "rinkeby": { + "message": "Testno omrežje Rinkeby" }, - "connectingToRinkeby": { - "message": "Připojuji se k Rinkeby Test Network" + "ropsten": { + "message": "Testno omrežje Ropsten" }, - "connectingToUnknown": { - "message": "Připojuji se k neznámé síti" + "rpc": { + "message": "RPC po meri" }, "sampleAccountName": { - "message": "Např. můj nový účet", + "message": "npr. Moj račun", "description": "Help user understand concept of adding a human-readable name to their account" }, "save": { - "message": "Uložit" - }, - "reprice_title": { - "message": "Změnit cenu transakce" + "message": "Shrani" }, - "reprice_subtitle": { - "message": "Navyšte cenu paliva ve snaze k přepsání a urychlení vyší transakce" + "saveAsCsvFile": { + "message": "Shrani kot CSV datoteko" }, "saveAsFile": { - "message": "Uložit do souboru", + "message": "Shrani kot datoteko", "description": "Account export process" }, "saveSeedAsFile": { - "message": "Uložit slova klíčové fráze do souboru" + "message": "Shrani seed words kot datoteko" + }, + "scanInstructions": { + "message": "Postavite QR kodo pred vašo kamero" + }, + "scanQrCode": { + "message": "Skeniraj QR kodo" }, "search": { - "message": "Hledat" + "message": "Iskanje" }, - "secretPhrase": { - "message": "Zadejte svých 12 slov tajné fráze k obnovení trezoru." + "searchResults": { + "message": "Rezultati iskanja" }, - "newPassword8Chars": { - "message": "Nové heslo (min 8 znaků)" + "secretPhrase": { + "message": "Vnesite vaših dvanajst besed za obnovitev vaših računov." }, "seedPhraseReq": { - "message": "klíčové fráze mají 12 slov" + "message": "Seed phrase mora biti dolg 12 besed" }, "select": { - "message": "Vybrat" + "message": "Izberi" }, "selectCurrency": { - "message": "Vybrat měnu" + "message": "Izberi valuto" + }, + "selectLocale": { + "message": "Izberi jezik" }, "selectService": { - "message": "Vybrat službu" + "message": "Izberi storitev" }, "selectType": { - "message": "Vybrat typ" + "message": "Izberi vrsto" }, "send": { - "message": "Odeslat" + "message": "Pošlji" }, "sendETH": { - "message": "Odeslat ETH" + "message": "Pošlji ETH" }, "sendTokens": { - "message": "Odeslat tokeny" + "message": "Pošlji žetone" }, - "onlySendToEtherAddress": { - "message": "Posílejte jen ETH na Ethereum adresu." + "sentEther": { + "message": "poslani ether" + }, + "sentTokens": { + "message": "poslani žetoni" + }, + "separateEachWord": { + "message": "Vsako besedo ločite z enim presledkom" }, "searchTokens": { - "message": "Hledat tokeny" + "message": "Iskanje žetonov" + }, + "selectAnAddress": { + "message": "Izberi naslov" + }, + "selectAnAccount": { + "message": "Izberi račun" + }, + "selectAnAccountHelp": { + "message": "Izberi račun za prikaz v MetaMask" + }, + "selectHdPath": { + "message": "Izberi HD Path" + }, + "selectPathHelp": { + "message": "Če obstoječih Ledger ne vidite, poskusite izbrati \"Legacy (MEW / MyCrypto)\"" }, "sendTokensAnywhere": { - "message": "Posílejte tokeny komukoli s Ethereum účtem" + "message": "Pošljite žetone komurkoli z Ethereum naslovom" }, "settings": { - "message": "Nastavení" - }, - "info": { - "message": "Informace" + "message": "Nastavitve" }, "shapeshiftBuy": { - "message": "Nakoupit na ShapeShift" + "message": "Kupi z Shapeshift" }, "showPrivateKeys": { - "message": "Zobrazit privátní klíče" + "message": "Pokaži zasebni ključ" }, "showQRCode": { - "message": "Zobrazit QR kód" + "message": "Pokaži QR kodo" + }, + "showHexData": { + "message": "Pokaži Hex Data" + }, + "showHexDataDescription": { + "message": "Izberite za prikaz hex podatkov na zaslonu za pošiljanje" }, "sign": { - "message": "Podepsat" + "message": "Podpiši" + }, + "signatureRequest": { + "message": "Zahteva za podpis" }, "signed": { - "message": "Podepsáno" + "message": "Podpisano" }, "signMessage": { - "message": "Podepsat zprávu" + "message": "Podpiši sporočilo" }, "signNotice": { - "message": "Podepsání zprávy může mít \nnebezpečný vedlejší učinek. Podepisujte zprávy pouze ze \nstránek, kterým plně důvěřujete celým svým účtem.\n Tato nebezpečná metoda bude odebrána v budoucí verzi. " + "message": "To podpisovanje lahko povzroči \nnevarne stranske učinke. Podpisujte samo sporočila \nstrani, ki jim zaupate s svojim celotnim računom.\n Ta nevarna funkcija bo odstranjena v prihodnji različici. " }, "sigRequest": { - "message": "Požadavek podpisu" + "message": "Zahteva za podpis" }, "sigRequested": { - "message": "Požádáno o podpis" + "message": "Podpis zahtevan" }, "spaceBetween": { - "message": "mezi slovy může být pouze mezera" + "message": "med besedami je lahko samo presledek" + }, + "speedUp": { + "message": "pospeši" + }, + "speedUpTitle": { + "message": "Pospeši transakcijo" + }, + "speedUpSubtitle": { + "message": "Povečajte gas prise za pospešitev transakcije" }, "status": { - "message": "Stav" + "message": "Status" }, "stateLogs": { - "message": "Stavové protokoly" + "message": "State dnevniki" }, "stateLogsDescription": { - "message": "Stavové protokoly obsahují vaše veřejné adresy účtů a odeslané transakce." + "message": "State vsebujejo vaš javni nasklov in zgodovino transakcij." }, "stateLogError": { - "message": "Chyba během získávání stavových protokolů." + "message": "Napaka pri pridobivanju state dnevnikov." + }, + "step1HardwareWallet": { + "message": "1. Povežite strojno denarnico" + }, + "step1HardwareWalletMsg": { + "message": "Z računalnikom povežite strojno denarnico." + }, + "step2HardwareWallet": { + "message": "2. Izberite račun" + }, + "step2HardwareWalletMsg": { + "message": "Izberite račun, ki ga želute uporabiti. Trenutno lahko izberete le en račun na enkrat." + }, + "step3HardwareWallet": { + "message": "3. Začnite uporabljati dApps in več!" + }, + "step3HardwareWalletMsg": { + "message": "Uporabite strojno denarnico kot katerikoli drug Ethereum račun. Prijavite se v dApps, pošljite Ether in ERC20 žetone in žetone kot CryptoKitties." }, "submit": { - "message": "Odeslat" + "message": "Potrdi" }, "submitted": { - "message": "Odesláno" + "message": "Potrjeno" }, "supportCenter": { - "message": "Navštivte naše centrum podpory" + "message": "Obiščite naše središče za podporo" }, "symbolBetweenZeroTen": { - "message": "Symbol musí být mezi 0 a 10 znaky." + "message": "Simbol mora imeti med 0 in 10 znakov." }, "takesTooLong": { - "message": "Trvá to dlouho?" + "message": "Trava predolgo?" }, "terms": { - "message": "Podmínky použití" + "message": "Pogoji uporabe" }, "testFaucet": { - "message": "Testovací faucet" + "message": "Testni faucet" }, "to": { - "message": "Komu" + "message": "Za" }, "toETHviaShapeShift": { - "message": "$1 na ETH přes ShapeShift", + "message": "$1 v ETH prek ShapeShift", "description": "system will fill in deposit type in start of message" }, + "token": { + "message": "Žeton" + }, "tokenAddress": { - "message": "Adresa tokenu" + "message": "Naslov žetona" }, "tokenAlreadyAdded": { - "message": "Token byl už přidán." + "message": "Žeton je bil že dodan." }, "tokenBalance": { - "message": "Váš zůstatek tokenu je:" + "message": "Vaš znesek žetonov je:" }, "tokenSelection": { - "message": "Vyhledejte token nebo je vyberte z našeho seznamu oblíbených tokenů." + "message": "Iščite žetone ali pa jih izberite iz seznama priljubljenih." }, "tokenSymbol": { - "message": "Symbol tokenu" + "message": "Simbol žetona" }, "tokenWarning1": { - "message": "Mějte přehled o tokenech, které jste koupili s účtem MetaMasku. Pokud jste koupili tokeny s jiným účtem, tyto tokeny se zde nezobrazí." + "message": "Sledite žetonom, ki ste jih kupili z MetaMask metamask računom. Če ste jih kupili drugje, ne bodo prikazani tukaj." }, "total": { - "message": "Celkem" + "message": "Skupno" + }, + "transaction": { + "message": "transakcija" + }, + "transactionConfirmed": { + "message": "Transakcija potrjena na $2." + }, + "transactionCreated": { + "message": "Transakcija z vrednostjo $1 ustvarjena na $2." + }, + "transactionWithNonce": { + "message": "Transakcija $1" + }, + "transactionDropped": { + "message": "Transakcija na $2 preklicana." + }, + "transactionSubmitted": { + "message": "Transakcija na $2 oddana." + }, + "transactionUpdated": { + "message": "Transakcija na $2 spremenjena." + }, + "transactionUpdatedGas": { + "message": "Transakcija spremenjena z gas price $1 na $2." + }, + "transactionErrored": { + "message": "Napaka pri transakciji." }, "transactions": { - "message": "transakce" + "message": "transakcije" }, "transactionError": { - "message": "Chyba transakce. Vyhozena výjimka v kódu kontraktu." + "message": "Napaka pri transakciji. Izjema sprožena v kodi pogodbe." }, "transactionMemo": { - "message": "Poznámka transakce (nepovinné)" + "message": "Opomba transakcije (nezahtevano)" }, "transactionNumber": { - "message": "Číslo transakce" + "message": "Številka transakcije" + }, + "transfer": { + "message": "Prenesi" + }, + "transferFrom": { + "message": "Prenesi od" }, "transfers": { - "message": "Převody" + "message": "Prenosi" + }, + "trezorHardwareWallet": { + "message": "Strojna denarnica TREZOR" }, "troubleTokenBalances": { - "message": "Měli jsme problém s načtením vašich tokenových zůstatků. Můžete je vidět ", + "message": "Če imate težave pri ogledu zneskov žetonov si jih lahko ogledate ", "description": "Followed by a link (here) to view token balances" }, + "tryAgain": { + "message": "Poskusi znova" + }, "twelveWords": { - "message": "Těchto 12 slov je jedinou možností, jak obnovit MetaMask účet. \nUložte je na bezpečné a neveřejné místo." + "message": "Edini način za obnovitev MetaMask računa, je teh 12 besed.\nShranite jih na varno in skrivno mesto." }, "typePassword": { - "message": "Zadejte své heslo" + "message": "Vnesite vaše MetaMask geslo" }, "uiWelcome": { - "message": "Vítejte v novém rozhraní (Beta)" + "message": "Dobrodošli v nov UI (Beta)" }, "uiWelcomeMessage": { - "message": "Používáte nyní nové rozhraní MetaMasku. Rozhlédněte se kolem, vyzkoušejte nové funkce, jako jsou zasílání tokenů, a dejte nám vědět, pokud narazíte na problém." + "message": "Uporabljate nov uporabniški vmesnik." }, "unapproved": { - "message": "Neschváleno" + "message": "Neodobreno" }, "unavailable": { - "message": "Nedostupné" + "message": "Ni na voljo" + }, + "units": { + "message": "enote" }, "unknown": { - "message": "Neznámé" + "message": "Neznano" + }, + "unknownFunction": { + "message": "Neznana funkcija" }, "unknownNetwork": { - "message": "Neznámá soukromá síť" + "message": "Neznano zasebno omrežje" }, "unknownNetworkId": { - "message": "Neznámé ID sítě" + "message": "Neznan ID omrežja" + }, + "unknownQrCode": { + "message": "Napaka: Te QR kode ni bilo mogoče prepoznati" + }, + "unknownCameraErrorTitle": { + "message": "Ooops! Nekaj je šlo narobe ...." + }, + "unknownCameraError": { + "message": "Pri dostopanju do vaše kamere je prišlo do napake. Poskusite znova kasneje ..." + }, + "unlock": { + "message": "Odkleni" + }, + "unlockMessage": { + "message": "Decentralizirana spletna denarnica" + }, + "updatedWithDate": { + "message": "Posodobljeno $1" }, "uriErrorMsg": { - "message": "URI vyžadují korektní HTTP/HTTPS prefix." + "message": "URI zahtevajo ustrezno HTTP/HTTPS predpono." }, "usaOnly": { - "message": "jen v USA", + "message": "Samo za ZDA", "description": "Using this exchange is limited to people inside the USA" }, "usedByClients": { - "message": "Používána různými klienty" + "message": "Uporabljen s strani večih različnih odjemalcev" }, "useOldUI": { - "message": "Použijte staré rozhraní" + "message": "Uporabi star UI" }, "validFileImport": { - "message": "Musíte vybrat validní soubor k importu." + "message": "Za uvoz morate izbrati pravilno datoteko." }, "vaultCreated": { - "message": "Trezor vytvořen" + "message": "Račun je ustvarjen" }, "viewAccount": { - "message": "Zobrazit účet" + "message": "Poglej račun" + }, + "viewOnEtherscan": { + "message": "Poglej na Etherscan" }, "visitWebSite": { - "message": "Navštivte naši stránku" + "message": "Obiščite našo spletno stran" + }, + "walletSeed": { + "message": "Wallet Seed" }, "warning": { - "message": "Varování" + "message": "Opozorilo" + }, + "welcomeBack": { + "message": "Dobrodošli nazaj!" }, "welcomeBeta": { - "message": "Vítejte v MetaMask Beta" + "message": "Dobrodošli v MetaMask Beta" }, "whatsThis": { - "message": "Co to je?" + "message": "Kaj je to?" + }, + "yesLetsTry": { + "message": "Pa poskusimo" + }, + "youNeedToAllowCameraAccess": { + "message": "Za uporabo te funkcije boste potrebovali dostop do kamere." }, "yourSigRequested": { - "message": "Je vyžadován váš podpis" + "message": "Zahtevan je bil vaš podpis" }, "youSign": { - "message": "Podepisujete" + "message": "Podpisali boste" + }, + "yourPrivateSeedPhrase": { + "message": "Vaš zasebni seed phrase" } -}
\ No newline at end of file +} diff --git a/app/home.html b/app/home.html index 4fad0f993..051133cf8 100644 --- a/app/home.html +++ b/app/home.html @@ -7,6 +7,7 @@ </head> <body> <div id="app-content"></div> + <script src="./libs.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/images/double-arrow.svg b/app/images/double-arrow.svg new file mode 100644 index 000000000..a31a0550b --- /dev/null +++ b/app/images/double-arrow.svg @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="12px" height="8px" viewBox="0 0 12 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch --> + <title>first/last</title> + <desc>Created with Sketch.</desc> + <defs></defs> + <g id="Action-Screens" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="first/last" fill="#5F5C5D"> + <polygon id="Path-12-Copy" points="12 0 12 8 6 4"></polygon> + <polygon id="Path-12-Copy-2" points="6 0 6 8 0 4"></polygon> + </g> + </g> +</svg>
\ No newline at end of file diff --git a/app/images/single-arrow.svg b/app/images/single-arrow.svg new file mode 100644 index 000000000..399da72d6 --- /dev/null +++ b/app/images/single-arrow.svg @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="6px" height="8px" viewBox="0 0 6 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch --> + <title>previous/next</title> + <desc>Created with Sketch.</desc> + <defs></defs> + <g id="Action-Screens" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <polygon id="previous/next" fill="#5F5C5D" points="6 0 6 8 0 4"></polygon> + </g> +</svg>
\ No newline at end of file diff --git a/app/manifest.json b/app/manifest.json index a72d474b1..89a843ffa 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "5.0.0", + "version": "5.0.2", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", diff --git a/app/notification.html b/app/notification.html index 457ba7137..042ffa6b8 100644 --- a/app/notification.html +++ b/app/notification.html @@ -11,6 +11,7 @@ </head> <body class="notification" style="height:600px;"> <div id="app-content"></div> + <script src="./libs.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 3acfd8c55..1ba0fda94 100644 --- a/app/popup.html +++ b/app/popup.html @@ -7,6 +7,7 @@ </head> <body style="width:357px; height:600px;"> <div id="app-content"></div> + <script src="./libs.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 2dcb79bef..d577ead41 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -448,6 +448,7 @@ function triggerUi () { const currentlyActiveMetamaskTab = Boolean(tabs.find(tab => openMetamaskTabsIDs[tab.id])) if (!popupIsOpen && !currentlyActiveMetamaskTab && !notificationIsOpen) { notificationManager.showPopup() + notificationIsOpen = true } }) } diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 1a10cdb34..ee38ee3ab 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -147,24 +147,28 @@ function listenForProviderRequest () { } }) - extension.runtime.onMessage.addListener(({ action = '', isApproved, caching, isUnlocked }) => { + extension.runtime.onMessage.addListener(({ action = '', isApproved, caching, isUnlocked, selectedAddress }) => { switch (action) { case 'approve-provider-request': isEnabled = true - injectScript(`window.dispatchEvent(new CustomEvent('ethereumprovider', { detail: {}}))`) + window.postMessage({ type: 'ethereumprovider', selectedAddress }, '*') + break + case 'approve-legacy-provider-request': + isEnabled = true + window.postMessage({ type: 'ethereumproviderlegacy', selectedAddress }, '*') break case 'reject-provider-request': - injectScript(`window.dispatchEvent(new CustomEvent('ethereumprovider', { detail: { error: 'User rejected provider access' }}))`) + window.postMessage({ type: 'ethereumprovider', error: 'User rejected provider access' }, '*') break case 'answer-is-approved': - injectScript(`window.dispatchEvent(new CustomEvent('ethereumisapproved', { detail: { isApproved: ${isApproved}, caching: ${caching}}}))`) + window.postMessage({ type: 'ethereumisapproved', isApproved, caching }, '*') break case 'answer-is-unlocked': - injectScript(`window.dispatchEvent(new CustomEvent('metamaskisunlocked', { detail: { isUnlocked: ${isUnlocked}}}))`) + window.postMessage({ type: 'metamaskisunlocked', isUnlocked }, '*') break case 'metamask-set-locked': isEnabled = false - injectScript(`window.dispatchEvent(new CustomEvent('metamasksetlocked', { detail: {}}))`) + window.postMessage({ type: 'metamasksetlocked' }, '*') break } }) diff --git a/app/scripts/controllers/provider-approval.js b/app/scripts/controllers/provider-approval.js index d3b7f6dff..21d7fd22e 100644 --- a/app/scripts/controllers/provider-approval.js +++ b/app/scripts/controllers/provider-approval.js @@ -88,7 +88,10 @@ class ProviderApprovalController { _handlePrivacyRequest () { const privacyMode = this.preferencesController.getFeatureFlags().privacyMode if (!privacyMode) { - this.platform && this.platform.sendMessage({ action: 'approve-provider-request' }, { active: true }) + this.platform && this.platform.sendMessage({ + action: 'approve-legacy-provider-request', + selectedAddress: this.publicConfigStore.getState().selectedAddress, + }, { active: true }) this.publicConfigStore.emit('update', this.publicConfigStore.getState()) } } @@ -101,7 +104,10 @@ class ProviderApprovalController { approveProviderRequest (origin) { this.closePopup && this.closePopup() const requests = this.store.getState().providerRequests || [] - this.platform && this.platform.sendMessage({ action: 'approve-provider-request' }, { active: true }) + this.platform && this.platform.sendMessage({ + action: 'approve-provider-request', + selectedAddress: this.publicConfigStore.getState().selectedAddress, + }, { active: true }) this.publicConfigStore.emit('update', this.publicConfigStore.getState()) const providerRequests = requests.filter(request => request.origin !== origin) this.store.updateState({ providerRequests }) diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js index b6f084841..a8936f13b 100644 --- a/app/scripts/controllers/token-rates.js +++ b/app/scripts/controllers/token-rates.js @@ -14,8 +14,9 @@ class TokenRatesController { * * @param {Object} [config] - Options to configure controller */ - constructor ({ interval = DEFAULT_INTERVAL, preferences } = {}) { + constructor ({ interval = DEFAULT_INTERVAL, currency, preferences } = {}) { this.store = new ObservableStore() + this.currency = currency this.preferences = preferences this.interval = interval } @@ -26,33 +27,24 @@ class TokenRatesController { async updateExchangeRates () { if (!this.isActive) { return } const contractExchangeRates = {} - // copy array to ensure its not modified during iteration - const tokens = this._tokens.slice() - for (const token of tokens) { - if (!token) return log.error(`TokenRatesController - invalid tokens state:\n${JSON.stringify(tokens, null, 2)}`) - const address = token.address - contractExchangeRates[address] = await this.fetchExchangeRate(address) + const nativeCurrency = this.currency ? this.currency.getState().nativeCurrency.toUpperCase() : 'ETH' + const pairs = this._tokens.map(token => `pairs[]=${token.address}/${nativeCurrency}`) + const query = pairs.join('&') + if (this._tokens.length > 0) { + try { + const response = await fetch(`https://exchanges.balanc3.net/pie?${query}&autoConversion=true`) + const { prices = [] } = await response.json() + prices.forEach(({ pair, price }) => { + contractExchangeRates[pair.split('/')[0]] = typeof price === 'number' ? price : 0 + }) + } catch (error) { + log.warn(`MetaMask - TokenRatesController exchange rate fetch failed.`, error) + } } this.store.putState({ contractExchangeRates }) } /** - * Fetches a token exchange rate by address - * - * @param {String} address - Token contract address - */ - async fetchExchangeRate (address) { - try { - const response = await fetch(`https://metamask.balanc3.net/prices?from=${address}&to=ETH&autoConversion=false&summaryOnly=true`) - const json = await response.json() - return json && json.length ? json[0].averagePrice : 0 - } catch (error) { - log.warn(`MetaMask - TokenRatesController exchange rate fetch failed for ${address}.`, error) - return 0 - } - } - - /** * @type {Number} */ set interval (interval) { diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 327e25042..83392761e 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -22,6 +22,21 @@ console.warn('ATTENTION: In an effort to improve user privacy, MetaMask ' + 'accounts. Please see https://bit.ly/2QQHXvF for complete information and up-to-date ' + 'example code.') +/** + * Adds a postMessage listener for a specific message type + * + * @param {string} messageType - postMessage type to listen for + * @param {Function} handler - event handler + * @param {boolean} remove - removes this handler after being triggered + */ +function onMessage(messageType, handler, remove) { + window.addEventListener('message', function ({ data }) { + if (!data || data.type !== messageType) { return } + remove && window.removeEventListener('message', handler) + handler.apply(window, arguments) + }) +} + // // setup plugin communication // @@ -39,52 +54,36 @@ var inpageProvider = new MetamaskInpageProvider(metamaskStream) inpageProvider.setMaxListeners(100) // set up a listener for when MetaMask is locked -window.addEventListener('metamasksetlocked', () => { - isEnabled = false -}) +onMessage('metamasksetlocked', () => { isEnabled = false }) + +// set up a listener for privacy mode responses +onMessage('ethereumproviderlegacy', ({ data: { selectedAddress } }) => { + isEnabled = true + inpageProvider.publicConfigStore.updateState({ selectedAddress }) +}, true) // augment the provider with its enable method inpageProvider.enable = function ({ force } = {}) { return new Promise((resolve, reject) => { - window.removeEventListener('ethereumprovider', providerHandle) - providerHandle = ({ detail }) => { - if (typeof detail.error !== 'undefined') { - reject(detail.error) + providerHandle = ({ data: { error, selectedAddress } }) => { + if (typeof error !== 'undefined') { + reject(error) } else { - // wait for the publicConfig store to populate with an account - const publicConfig = new Promise((resolve) => { - const { selectedAddress } = inpageProvider.publicConfigStore.getState() - inpageProvider._metamask.isUnlocked().then(unlocked => { - if (!unlocked || selectedAddress) { - resolve() - } else { - inpageProvider.publicConfigStore.on('update', ({ selectedAddress }) => { - selectedAddress && resolve() - }) - } - }) - }) + window.removeEventListener('message', providerHandle) + inpageProvider.publicConfigStore.updateState({ selectedAddress }) // wait for the background to update with an account - const ethAccounts = new Promise((resolveAccounts, rejectAccounts) => { - inpageProvider.sendAsync({ method: 'eth_accounts', params: [] }, (error, response) => { - if (error) { - rejectAccounts(error) - } else { - resolveAccounts(response.result) - } - }) - }) - - Promise.all([ethAccounts, publicConfig]) - .then(([selectedAddress]) => { + inpageProvider.sendAsync({ method: 'eth_accounts', params: [] }, (error, response) => { + if (error) { + reject(error) + } else { isEnabled = true - resolve(selectedAddress) - }) - .catch(reject) + resolve(response.result) + } + }) } } - window.addEventListener('ethereumprovider', providerHandle) + onMessage('ethereumprovider', providerHandle, true) window.postMessage({ type: 'ETHEREUM_ENABLE_PROVIDER', force }, '*') }) } @@ -106,20 +105,15 @@ inpageProvider._metamask = new Proxy({ * @returns {Promise<boolean>} - Promise resolving to true if this domain has been previously approved */ isApproved: function() { - return new Promise((resolve, reject) => { - window.removeEventListener('ethereumisapproved', isApprovedHandle) - isApprovedHandle = ({ detail }) => { - if (typeof detail.error !== 'undefined') { - reject(detail.error) + return new Promise((resolve) => { + isApprovedHandle = ({ data: { caching, isApproved } }) => { + if (caching) { + resolve(!!isApproved) } else { - if (detail.caching) { - resolve(!!detail.isApproved) - } else { - resolve(false) - } + resolve(false) } } - window.addEventListener('ethereumisapproved', isApprovedHandle) + onMessage('ethereumisapproved', isApprovedHandle, true) window.postMessage({ type: 'ETHEREUM_IS_APPROVED' }, '*') }) }, @@ -130,16 +124,11 @@ inpageProvider._metamask = new Proxy({ * @returns {Promise<boolean>} - Promise resolving to true if MetaMask is currently unlocked */ isUnlocked: function () { - return new Promise((resolve, reject) => { - window.removeEventListener('metamaskisunlocked', isUnlockedHandle) - isUnlockedHandle = ({ detail }) => { - if (typeof detail.error !== 'undefined') { - reject(detail.error) - } else { - resolve(!!detail.isUnlocked) - } + return new Promise((resolve) => { + isUnlockedHandle = ({ data: { isUnlocked } }) => { + resolve(!!isUnlocked) } - window.addEventListener('metamaskisunlocked', isUnlockedHandle) + onMessage('metamaskisunlocked', isUnlockedHandle, true) window.postMessage({ type: 'METAMASK_IS_UNLOCKED' }, '*') }) }, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 5ae0f608d..641a7ef3f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -117,6 +117,7 @@ module.exports = class MetamaskController extends EventEmitter { // token exchange rate tracker this.tokenRatesController = new TokenRatesController({ + currency: this.currencyController.store, preferences: this.preferencesController.store, }) diff --git a/development/states/navigate-txs.json b/development/states/navigate-txs.json new file mode 100644 index 000000000..d8b8dc67f --- /dev/null +++ b/development/states/navigate-txs.json @@ -0,0 +1,323 @@ +{ + "metamask": { + "isInitialized": true, + "isUnlocked": true, + "isAccountMenuOpen": false, + "isMascara": false, + "isPopup": false, + "rpcTarget": "https://rawtestrpc.metamask.io/", + "identities": { + "0x8cf82b5aa41ff2282427be151dd328568684007a": { + "address": "0x8cf82b5aa41ff2282427be151dd328568684007a", + "name": "Account 3" + }, + "0xbe1a00e10ec68b154adb84e8119167146a71c9a2": { + "address": "0xbe1a00e10ec68b154adb84e8119167146a71c9a2", + "name": "Account 2" + }, + "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2": { + "address": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "name": "Account 1" + } + }, + "unapprovedTxs": { + "2389644572638771": { + "estimatedGas": "0x8544", + "gasLimitSpecified": true, + "gasPriceSpecified": true, + "history": [], + "id": 2389644572638771, + "loadingDefaults": false, + "metamaskNetworkId": "4", + "origin": "MetaMask", + "status": "unapproved", + "time": 1538844175144, + "txParams": { + "data": "0xa9059cbb000000000000000000000000be1a00e10ec68b154adb84e8119167146a71c9a20000000000000000000000000000000000000000000000000000000000000000", + "from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "gas": "0x8544", + "gasPrice": "0x3b9aca00", + "to": "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a", + "value": "0x0" + }, + "type": "standard" + }, + "2389644572638772": { + "estimatedGas": "0x5208", + "gasLimitSpecified": true, + "gasPriceSpecified": true, + "history": [], + "id": 2389644572638772, + "loadingDefaults": false, + "metamaskNetworkId": "4", + "origin": "MetaMask", + "status": "unapproved", + "time": 1538844178492, + "txParams": { + "from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "gas": "0x5208", + "gasPrice": "0x3b9aca00", + "to": "0xbe1a00e10ec68b154adb84e8119167146a71c9a2", + "value": "0x0" + }, + "type": "standard" + }, + "2389644572638773": { + "estimatedGas": { + "length": 1, + "negative": 0, + "red": null, + "words": [ + 34061, + null + ] + }, + "gasLimitSpecified": false, + "gasPriceSpecified": true, + "history": [], + "id": 2389644572638773, + "loadingDefaults": false, + "metamaskNetworkId": "4", + "origin": "localhost", + "status": "unapproved", + "time": 1538844204724, + "txParams": { + "data": "0xdfb29935000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000155468697320697320746865206970667320686173680000000000000000000000", + "from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "gas": "0xc793", + "gasPrice": "0x3b9aca00", + "to": "0xb7ec370c889b3b48ec537e0b2c887faedceb254a", + "value": "0x0" + }, + "type": "standard" + }, + "2389644572638774": { + "estimatedGas": "0x38f53", + "gasLimitSpecified": true, + "gasPriceSpecified": false, + "history": [], + "id": 2389644572638774, + "loadingDefaults": false, + "metamaskNetworkId": "4", + "origin": "remix.ethereum.org", + "status": "unapproved", + "time": 1538844223352, + "txParams": { + "data": "0x608060405234801561001057600080fd5b506102a7806100206000396000f30060806040526004361061004b5763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663d13319c48114610050578063dfb29935146100da575b600080fd5b34801561005c57600080fd5b50610065610135565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561009f578181015183820152602001610087565b50505050905090810190601f1680156100cc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156100e657600080fd5b506040805160206004803580820135601f81018490048402850184019095528484526101339436949293602493928401919081908401838280828437509497506101cc9650505050505050565b005b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101c15780601f10610196576101008083540402835291602001916101c1565b820191906000526020600020905b8154815290600101906020018083116101a457829003601f168201915b505050505090505b90565b80516101df9060009060208401906101e3565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061022457805160ff1916838001178555610251565b82800160010185558215610251579182015b82811115610251578251825591602001919060010190610236565b5061025d929150610261565b5090565b6101c991905b8082111561025d57600081556001016102675600a165627a7a72305820cf4282c534b8f2faad947d592afa109b907e4e6b2f52335b361b69c24fedb9580029", + "from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "gas": "0x38f53", + "gasPrice": "0x3b9aca00", + "value": "0x0" + }, + "type": "standard" + } + }, + "noActiveNotices": true, + "frequentRpcList": [], + "addressBook": [], + "selectedTokenAddress": null, + "contractExchangeRates": {}, + "tokenExchangeRates": {}, + "tokens": [ + { + "address": "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a", + "decimals": 9, + "symbol": "DGD" + } + ], + "pendingTokens": {}, + "send": { + "gasLimit": null, + "gasPrice": null, + "gasTotal": null, + "tokenBalance": null, + "from": "", + "to": "", + "amount": "0x0", + "memo": "", + "errors": {}, + "editingTransactionId": null, + "forceGasMin": null + }, + "coinOptions": {}, + "useBlockie": false, + "featureFlags": { + "betaUI": true, + "skipAnnounceBetaUI": true + }, + "isRevealingSeedWords": false, + "welcomeScreenSeen": false, + "currentLocale": "en", + "preferences": { + "useETHAsPrimaryCurrency": true + }, + "provider": { + "type": "rinkeby" + }, + "network": "4", + "accounts": { + "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2": { + "address": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "balance": "0x36aabfb2a0190c00" + }, + "0xbe1a00e10ec68b154adb84e8119167146a71c9a2": { + "address": "0xbe1a00e10ec68b154adb84e8119167146a71c9a2", + "balance": "0x7b3ef08c294a000" + }, + "0x8cf82b5aa41ff2282427be151dd328568684007a": { + "address": "0x8cf82b5aa41ff2282427be151dd328568684007a", + "balance": "0x0" + } + }, + "currentBlockGasLimit": "0x731e25", + "selectedAddressTxList": [], + "computedBalances": {}, + "unapprovedMsgs": {}, + "unapprovedMsgCount": 0, + "unapprovedPersonalMsgs": {}, + "unapprovedPersonalMsgCount": 0, + "unapprovedTypedMessages": {}, + "unapprovedTypedMessagesCount": 0, + "keyringTypes": [ + "Simple Key Pair", + "HD Key Tree", + "Trezor Hardware", + "Ledger Hardware" + ], + "keyrings": [ + { + "type": "HD Key Tree", + "accounts": [ + "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "0xbe1a00e10ec68b154adb84e8119167146a71c9a2", + "0x8cf82b5aa41ff2282427be151dd328568684007a" + ] + } + ], + "currentAccountTab": "history", + "accountTokens": { + "0x8cf82b5aa41ff2282427be151dd328568684007a": {}, + "0xbe1a00e10ec68b154adb84e8119167146a71c9a2": {}, + "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2": { + "rinkeby": [ + { + "address": "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a", + "decimals": 9, + "symbol": "DGD" + }, + { + "address": "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359", + "decimals": 18, + "symbol": "DAI" + } + ] + } + }, + "assetImages": { + "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359": null, + "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a": null + }, + "suggestedTokens": {}, + "lostIdentities": {}, + "seedWords": null, + "forgottenPassword": false, + "selectedAddress": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "recentBlocks": [], + "currentCurrency": "usd", + "conversionRate": 225.23, + "conversionDate": 1538859376, + "shapeShiftTxList": [], + "infuraNetworkStatus": { + "kovan": "ok", + "mainnet": "ok", + "rinkeby": "ok", + "ropsten": "ok" + }, + "lostAccounts": [] + }, + "appState": { + "shouldClose": false, + "menuOpen": false, + "modal": { + "open": false, + "modalState": { + "name": null, + "props": {} + }, + "previousModalState": { + "name": null + } + }, + "sidebar": { + "isOpen": false, + "transitionName": "", + "type": "" + }, + "alertOpen": false, + "alertMessage": null, + "qrCodeData": null, + "networkDropdownOpen": false, + "currentView": { + "name": "confTx", + "context": 0 + }, + "accountDetail": { + "subview": "transactions" + }, + "transForward": false, + "isLoading": false, + "warning": null, + "buyView": {}, + "isMouseUser": true, + "gasIsLoading": false, + "networkNonce": "0x92", + "defaultHdPaths": { + "trezor": "m/44'/60'/0'/0", + "ledger": "m/44'/60'/0'/0/0" + } + }, + "localeMessages": {}, + "send": { + "fromDropdownOpen": false, + "toDropdownOpen": false, + "errors": {} + }, + "confirmTransaction": { + "txData": { + "estimatedGas": "0x38f53", + "gasLimitSpecified": true, + "gasPriceSpecified": false, + "history": [], + "id": 2389644572638774, + "loadingDefaults": false, + "metamaskNetworkId": "4", + "origin": "remix.ethereum.org", + "status": "unapproved", + "time": 1538844223352, + "txParams": { + "data": "0x608060405234801561001057600080fd5b506102a7806100206000396000f30060806040526004361061004b5763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663d13319c48114610050578063dfb29935146100da575b600080fd5b34801561005c57600080fd5b50610065610135565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561009f578181015183820152602001610087565b50505050905090810190601f1680156100cc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156100e657600080fd5b506040805160206004803580820135601f81018490048402850184019095528484526101339436949293602493928401919081908401838280828437509497506101cc9650505050505050565b005b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101c15780601f10610196576101008083540402835291602001916101c1565b820191906000526020600020905b8154815290600101906020018083116101a457829003601f168201915b505050505090505b90565b80516101df9060009060208401906101e3565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061022457805160ff1916838001178555610251565b82800160010185558215610251579182015b82811115610251578251825591602001919060010190610236565b5061025d929150610261565b5090565b6101c991905b8082111561025d57600081556001016102675600a165627a7a72305820cf4282c534b8f2faad947d592afa109b907e4e6b2f52335b361b69c24fedb9580029", + "from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2", + "gas": "0x38f53", + "gasPrice": "0x3b9aca00", + "value": "0x0" + }, + "type": "standard" + }, + "tokenData": {}, + "methodData": {}, + "tokenProps": { + "tokenDecimals": "", + "tokenSymbol": "" + }, + "fiatTransactionAmount": "0", + "fiatTransactionFee": "0.05", + "fiatTransactionTotal": "0.05", + "ethTransactionAmount": "0", + "ethTransactionFee": "0.000233", + "ethTransactionTotal": "0.000233", + "hexGasTotal": "0xd42f28057e00", + "nonce": "", + "toSmartContract": false, + "fetchingData": false + } +}
\ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index c3d9019ff..1487f717e 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -26,6 +26,16 @@ const pify = require('pify') const gulpMultiProcess = require('gulp-multi-process') const endOfStream = pify(require('end-of-stream')) +const packageJSON = require('./package.json') +const dependencies = Object.keys(packageJSON && packageJSON.dependencies || {}) +const materialUIDependencies = ['@material-ui/core'] +const reactDepenendencies = dependencies.filter(dep => dep.match(/react/)) + +const uiDependenciesToBundle = [ + ...materialUIDependencies, + ...reactDepenendencies, +] + function gulpParallel (...args) { return function spawnGulpChildProcess (cb) { return gulpMultiProcess(args, cb, true) @@ -283,11 +293,32 @@ const buildJsFiles = [ ] // bundle tasks +createTasksForBuildJsUIDeps({ dependenciesToBundle: uiDependenciesToBundle, filename: 'libs' }) createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'dev:extension:js', devMode: true }) createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'build:extension:js' }) createTasksForBuildJsMascara({ taskPrefix: 'build:mascara:js' }) createTasksForBuildJsMascara({ taskPrefix: 'dev:mascara:js', devMode: true }) +function createTasksForBuildJsUIDeps ({ dependenciesToBundle, filename }) { + const destinations = browserPlatforms.map(platform => `./dist/${platform}`) + + const bundleTaskOpts = Object.assign({ + buildSourceMaps: true, + sourceMapDir: '../sourcemaps', + minifyBuild: true, + devMode: false, + }) + + gulp.task('build:extension:js:uideps', bundleTask(Object.assign({ + label: filename, + filename: `${filename}.js`, + destinations, + buildLib: true, + dependenciesToBundle: uiDependenciesToBundle, + }, bundleTaskOpts))) +} + + function createTasksForBuildJsExtension ({ buildJsFiles, taskPrefix, devMode, bundleTaskOpts = {} }) { // inpage must be built before all other scripts: const rootDir = './app/scripts' @@ -330,6 +361,7 @@ function createTasksForBuildJs ({ rootDir, taskPrefix, bundleTaskOpts, destinati label: jsFile, filename: `${jsFile}.js`, filepath: `${rootDir}/${jsFile}.js`, + externalDependencies: jsFile === 'ui' && !bundleTaskOpts.devMode && uiDependenciesToBundle, destinations, }, bundleTaskOpts))) }) @@ -406,6 +438,7 @@ gulp.task('build', 'clean', 'build:scss', gulpParallel( + 'build:extension:js:uideps', 'build:extension:js', 'build:mascara:js', 'copy' @@ -454,14 +487,25 @@ function zipTask (target) { function generateBundler (opts, performBundle) { const browserifyOpts = assign({}, watchify.args, { - entries: [opts.filepath], plugin: 'browserify-derequire', debug: opts.buildSourceMaps, fullPaths: opts.buildWithFullPaths, }) + if (!opts.buildLib) { + browserifyOpts['entries'] = [opts.filepath] + } + let bundler = browserify(browserifyOpts) + if (opts.buildLib) { + bundler = bundler.require(opts.dependenciesToBundle) + } + + if (opts.externalDependencies) { + bundler = bundler.external(opts.externalDependencies) + } + // inject variables into bundle bundler.transform(envify({ METAMASK_DEBUG: opts.devMode, diff --git a/old-ui/app/accounts/import/index.js b/old-ui/app/accounts/import/index.js index a57525ccf..b2c3a4a18 100644 --- a/old-ui/app/accounts/import/index.js +++ b/old-ui/app/accounts/import/index.js @@ -59,7 +59,7 @@ AccountImportSubview.prototype.render = function () { }, onClick: () => { global.platform.openWindow({ - url: 'https://metamask.helpscoutdocs.com/article/17-what-are-loose-accounts', + url: 'https://metamask.zendesk.com/hc/en-us/articles/360015289592-What-Are-Loose-Accounts-', }) }, }, 'here.'), diff --git a/old-ui/app/config.js b/old-ui/app/config.js index 2a0643919..91239f19c 100644 --- a/old-ui/app/config.js +++ b/old-ui/app/config.js @@ -288,7 +288,7 @@ ConfigScreen.prototype.render = function () { }, [ 'Resetting is for developer use only. ', h('a', { - href: 'http://metamask.helpscoutdocs.com/article/36-resetting-an-account', + href: 'https://metamask.zendesk.com/hc/en-us/articles/360015489231-Resetting-an-Account-Old-UI-', target: '_blank', }, 'Read more.'), ]), diff --git a/test/e2e/beta/from-import-beta-ui.spec.js b/test/e2e/beta/from-import-beta-ui.spec.js index caef99fed..ffb0a10ff 100644 --- a/test/e2e/beta/from-import-beta-ui.spec.js +++ b/test/e2e/beta/from-import-beta-ui.spec.js @@ -159,6 +159,7 @@ describe('Using MetaMask with an existing account', function () { it('clicks through the ToS', async () => { // terms of use + await delay(largeDelayMs) const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled() assert.equal(canClickThrough, false, 'disabled continue button') const bottomOfTos = await findElement(driver, By.linkText('Attributions')) diff --git a/test/e2e/beta/metamask-beta-responsive-ui.spec.js b/test/e2e/beta/metamask-beta-responsive-ui.spec.js new file mode 100644 index 000000000..b93563b25 --- /dev/null +++ b/test/e2e/beta/metamask-beta-responsive-ui.spec.js @@ -0,0 +1,360 @@ +const path = require('path') +const assert = require('assert') +const webdriver = require('selenium-webdriver') +const { By, until } = webdriver +const { + delay, + buildChromeWebDriver, + buildFirefoxWebdriver, + installWebExt, + getExtensionIdChrome, + getExtensionIdFirefox, +} = require('../func') +const { + checkBrowserForConsoleErrors, + closeAllWindowHandlesExcept, + findElement, + findElements, + loadExtension, + verboseReportOnFailure, +} = require('./helpers') + +describe('MetaMask', function () { + let extensionId + let driver + + const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' + const tinyDelayMs = 200 + const regularDelayMs = tinyDelayMs * 2 + const largeDelayMs = regularDelayMs * 2 + + this.timeout(0) + this.bail(true) + + before(async function () { + switch (process.env.SELENIUM_BROWSER) { + case 'chrome': { + const extPath = path.resolve('dist/chrome') + driver = buildChromeWebDriver(extPath, { responsive: true }) + extensionId = await getExtensionIdChrome(driver) + await driver.get(`chrome-extension://${extensionId}/popup.html`) + break + } + case 'firefox': { + const extPath = path.resolve('dist/firefox') + driver = buildFirefoxWebdriver({ responsive: true }) + await installWebExt(driver, extPath) + await delay(700) + extensionId = await getExtensionIdFirefox(driver) + await driver.get(`moz-extension://${extensionId}/popup.html`) + } + } + }) + + afterEach(async function () { + if (process.env.SELENIUM_BROWSER === 'chrome') { + const errors = await checkBrowserForConsoleErrors(driver) + if (errors.length) { + const errorReports = errors.map(err => err.message) + const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + console.error(new Error(errorMessage)) + } + } + if (this.currentTest.state === 'failed') { + await verboseReportOnFailure(driver, this.currentTest) + } + }) + + after(async function () { + await driver.quit() + }) + + describe('New UI setup', async function () { + it('switches to first tab', async function () { + await delay(tinyDelayMs) + const [firstTab] = await driver.getAllWindowHandles() + await driver.switchTo().window(firstTab) + await delay(regularDelayMs) + }) + + it('selects the new UI option', async () => { + try { + const overlay = await findElement(driver, By.css('.full-flex-height')) + await driver.wait(until.stalenessOf(overlay)) + } catch (e) {} + + let button + try { + button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]")) + } catch (e) { + await loadExtension(driver, extensionId) + await delay(largeDelayMs) + button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]")) + } + await button.click() + await delay(regularDelayMs) + + // Close all other tabs + const [tab0, tab1, tab2] = await driver.getAllWindowHandles() + await driver.switchTo().window(tab0) + await delay(tinyDelayMs) + + let selectedUrl = await driver.getCurrentUrl() + await delay(tinyDelayMs) + if (tab0 && selectedUrl.match(/popup.html/)) { + await closeAllWindowHandlesExcept(driver, tab0) + } else if (tab1) { + await driver.switchTo().window(tab1) + selectedUrl = await driver.getCurrentUrl() + await delay(tinyDelayMs) + if (selectedUrl.match(/popup.html/)) { + await closeAllWindowHandlesExcept(driver, tab1) + } else if (tab2) { + await driver.switchTo().window(tab2) + selectedUrl = await driver.getCurrentUrl() + selectedUrl.match(/popup.html/) && await closeAllWindowHandlesExcept(driver, tab2) + } + } else { + throw new Error('popup.html not found') + } + await delay(regularDelayMs) + const [appTab] = await driver.getAllWindowHandles() + await driver.switchTo().window(appTab) + await delay(tinyDelayMs) + + await loadExtension(driver, extensionId) + await delay(regularDelayMs) + + const continueBtn = await findElement(driver, By.css('.welcome-screen__button')) + await continueBtn.click() + await delay(regularDelayMs) + }) + }) + + describe('Going through the first time flow', () => { + it('accepts a secure password', async () => { + const passwordBox = await findElement(driver, By.css('.create-password #create-password')) + const passwordBoxConfirm = await findElement(driver, By.css('.create-password #confirm-password')) + const button = await findElement(driver, By.css('.create-password button')) + + await passwordBox.sendKeys('correct horse battery staple') + await passwordBoxConfirm.sendKeys('correct horse battery staple') + await button.click() + await delay(regularDelayMs) + }) + + it('clicks through the unique image screen', async () => { + const nextScreen = await findElement(driver, By.css('.unique-image button')) + await nextScreen.click() + await delay(regularDelayMs) + }) + + it('clicks through the ToS', async () => { + // terms of use + const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled() + assert.equal(canClickThrough, false, 'disabled continue button') + const bottomOfTos = await findElement(driver, By.linkText('Attributions')) + await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos) + await delay(regularDelayMs) + const acceptTos = await findElement(driver, By.css('.tou button')) + driver.wait(until.elementIsEnabled(acceptTos)) + await acceptTos.click() + await delay(regularDelayMs) + }) + + it('clicks through the privacy notice', async () => { + // privacy notice + const nextScreen = await findElement(driver, By.css('.tou button')) + await nextScreen.click() + await delay(regularDelayMs) + }) + + it('clicks through the phishing notice', async () => { + // phishing notice + const noticeElement = await driver.findElement(By.css('.markdown')) + await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', noticeElement) + await delay(regularDelayMs) + const nextScreen = await findElement(driver, By.css('.tou button')) + await nextScreen.click() + await delay(regularDelayMs) + }) + + let seedPhrase + + it('reveals the seed phrase', async () => { + const byRevealButton = By.css('.backup-phrase__secret-blocker .backup-phrase__reveal-button') + await driver.wait(until.elementLocated(byRevealButton, 10000)) + const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000) + await revealSeedPhraseButton.click() + await delay(regularDelayMs) + + seedPhrase = await driver.findElement(By.css('.backup-phrase__secret-words')).getText() + assert.equal(seedPhrase.split(' ').length, 12) + await delay(regularDelayMs) + + const nextScreen = await findElement(driver, By.css('.backup-phrase button')) + await nextScreen.click() + await delay(regularDelayMs) + }) + + async function clickWordAndWait (word) { + const xpathClass = 'backup-phrase__confirm-seed-option backup-phrase__confirm-seed-option--unselected' + const xpath = `//button[@class='${xpathClass}' and contains(text(), '${word}')]` + const word0 = await findElement(driver, By.xpath(xpath), 10000) + + await word0.click() + await delay(tinyDelayMs) + } + + async function retypeSeedPhrase (words, wasReloaded, count = 0) { + try { + if (wasReloaded) { + const byRevealButton = By.css('.backup-phrase__secret-blocker .backup-phrase__reveal-button') + await driver.wait(until.elementLocated(byRevealButton, 10000)) + const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000) + await revealSeedPhraseButton.click() + await delay(regularDelayMs) + + const nextScreen = await findElement(driver, By.css('.backup-phrase button')) + await nextScreen.click() + await delay(regularDelayMs) + } + + for (let i = 0; i < 12; i++) { + await clickWordAndWait(words[i]) + } + } catch (e) { + if (count > 2) { + throw e + } else { + await loadExtension(driver, extensionId) + await retypeSeedPhrase(words, true, count + 1) + } + } + } + + it('can retype the seed phrase', async () => { + const words = seedPhrase.split(' ') + + await retypeSeedPhrase(words) + + const confirm = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) + await confirm.click() + await delay(regularDelayMs) + }) + + it('clicks through the deposit modal', async () => { + const byBuyModal = By.css('span .modal') + const buyModal = await driver.wait(until.elementLocated(byBuyModal)) + const closeModal = await findElement(driver, By.css('.page-container__header-close')) + await closeModal.click() + await driver.wait(until.stalenessOf(buyModal)) + await delay(regularDelayMs) + }) + }) + + describe('Show account information', () => { + it('show account details dropdown menu', async () => { + await driver.findElement(By.css('div.menu-bar__open-in-browser')).click() + const options = await driver.findElements(By.css('div.menu.account-details-dropdown div.menu__item')) + assert.equal(options.length, 3) // HD Wallet type does not have to show the Remove Account option + await delay(regularDelayMs) + }) + }) + + describe('Import seed phrase', () => { + it('logs out of the vault', async () => { + await driver.findElement(By.css('.account-menu__icon')).click() + await delay(regularDelayMs) + + const logoutButton = await findElement(driver, By.css('.account-menu__logout-button')) + assert.equal(await logoutButton.getText(), 'Log out') + await logoutButton.click() + await delay(regularDelayMs) + }) + + it('imports seed phrase', async () => { + const restoreSeedLink = await findElement(driver, By.css('.unlock-page__link--import')) + assert.equal(await restoreSeedLink.getText(), 'Import using account seed phrase') + await restoreSeedLink.click() + await delay(regularDelayMs) + + const seedTextArea = await findElement(driver, By.css('textarea')) + await seedTextArea.sendKeys(testSeedPhrase) + await delay(regularDelayMs) + + const passwordInputs = await driver.findElements(By.css('input')) + await delay(regularDelayMs) + + await passwordInputs[0].sendKeys('correct horse battery staple') + await passwordInputs[1].sendKeys('correct horse battery staple') + await driver.findElement(By.css('.first-time-flow__button')).click() + await delay(regularDelayMs) + }) + + it('switches to localhost', async () => { + const networkDropdown = await findElement(driver, By.css('.network-name')) + await networkDropdown.click() + await delay(regularDelayMs) + + const [localhost] = await findElements(driver, By.xpath(`//span[contains(text(), 'Localhost')]`)) + await localhost.click() + await delay(largeDelayMs * 2) + }) + + it('balance renders', async () => { + const balance = await findElement(driver, By.css('.transaction-view-balance__primary-balance')) + await driver.wait(until.elementTextMatches(balance, /100\s*ETH/)) + await delay(regularDelayMs) + }) + }) + + describe('Send ETH from inside MetaMask', () => { + it('starts to send a transaction', async function () { + const sendButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`)) + await sendButton.click() + await delay(regularDelayMs) + + const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]')) + const inputAmount = await findElement(driver, By.css('.unit-input__input')) + await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') + await inputAmount.sendKeys('1') + + const inputValue = await inputAmount.getAttribute('value') + assert.equal(inputValue, '1') + + // Set the gas limit + const configureGas = await findElement(driver, By.css('.send-v2__gas-fee-display button')) + await configureGas.click() + await delay(regularDelayMs) + + const gasModal = await driver.findElement(By.css('span .modal')) + + const save = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`)) + await save.click() + await driver.wait(until.stalenessOf(gasModal)) + await delay(regularDelayMs) + + // Continue to next screen + const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), 'Next')]`)) + await nextScreen.click() + await delay(regularDelayMs) + }) + + it('confirms the transaction', async function () { + const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) + await confirmButton.click() + await delay(largeDelayMs) + }) + + it('finds the transaction in the transactions list', async function () { + const transactions = await findElements(driver, By.css('.transaction-list-item')) + assert.equal(transactions.length, 1) + + if (process.env.SELENIUM_BROWSER !== 'firefox') { + const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary')) + await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000) + } + }) + }) +}) diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js index 5887d0293..2b5c8ee18 100644 --- a/test/e2e/beta/metamask-beta-ui.spec.js +++ b/test/e2e/beta/metamask-beta-ui.spec.js @@ -271,17 +271,6 @@ describe('MetaMask', function () { await driver.wait(until.stalenessOf(accountModal)) await delay(regularDelayMs) }) - it('show account details dropdown menu', async () => { - - const {width, height} = await driver.manage().window().getSize() - driver.manage().window().setSize(320, 480) - await driver.findElement(By.css('div.menu-bar__open-in-browser')).click() - const options = await driver.findElements(By.css('div.menu.account-details-dropdown div.menu__item')) - assert.equal(options.length, 3) // HD Wallet type does not have to show the Remove Account option - await delay(regularDelayMs) - driver.manage().window().setSize(width, height) - - }) }) describe('Enable privacy mode', () => { @@ -495,6 +484,142 @@ describe('MetaMask', function () { }) }) + describe('Navigate transactions', () => { + it('adds multiple transactions', async () => { + await delay(regularDelayMs) + + await waitUntilXWindowHandles(driver, 2) + const windowHandles = await driver.getAllWindowHandles() + const extension = windowHandles[0] + const dapp = windowHandles[1] + + await driver.switchTo().window(dapp) + await delay(regularDelayMs) + + const send3eth = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`), 10000) + await send3eth.click() + await delay(regularDelayMs) + + const contractDeployment = await findElement(driver, By.xpath(`//button[contains(text(), 'Deploy Contract')]`), 10000) + await contractDeployment.click() + await delay(regularDelayMs) + + await send3eth.click() + await contractDeployment.click() + await delay(regularDelayMs) + + await driver.switchTo().window(extension) + await delay(regularDelayMs) + + const transactions = await findElements(driver, By.css('.transaction-list-item')) + await transactions[3].click() + await delay(regularDelayMs) + }) + + it('navigates the transactions', async () => { + let navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow')) + assert.equal(navigateTxButtons.length, 4, 'navigation button present') + + await navigateTxButtons[2].click() + let navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation')) + let navigationText = await navigationElement.getText() + assert.equal(navigationText.includes('2'), true, 'changed transaction right') + + navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow')) + await navigateTxButtons[2].click() + navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation')) + navigationText = await navigationElement.getText() + assert.equal(navigationText.includes('3'), true, 'changed transaction right') + + navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow')) + await navigateTxButtons[2].click() + navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation')) + navigationText = await navigationElement.getText() + assert.equal(navigationText.includes('4'), true, 'changed transaction right') + + navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow')) + await navigateTxButtons[0].click() + navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation')) + navigationText = await navigationElement.getText() + assert.equal(navigationText.includes('1'), true, 'navigate to first transaction') + + navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow')) + await navigateTxButtons[3].click() + navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation')) + navigationText = await navigationElement.getText() + assert.equal(navigationText.split('4').length, 3, 'navigate to last transaction') + + navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow')) + await navigateTxButtons[1].click() + navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation')) + navigationText = await navigationElement.getText() + assert.equal(navigationText.includes('3'), true, 'changed transaction left') + + navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow')) + await navigateTxButtons[1].click() + navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation')) + navigationText = await navigationElement.getText() + assert.equal(navigationText.includes('2'), true, 'changed transaction left') + }) + + it('adds a transaction while confirm screen is in focus', async () => { + let navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation')) + let navigationText = await navigationElement.getText() + assert.equal(navigationText.includes('2'), true, 'second transaction in focus') + + const windowHandles = await driver.getAllWindowHandles() + const extension = windowHandles[0] + const dapp = windowHandles[1] + + await driver.switchTo().window(dapp) + await delay(regularDelayMs) + + const send3eth = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`), 10000) + await send3eth.click() + await delay(regularDelayMs) + + await driver.switchTo().window(extension) + await delay(regularDelayMs) + + navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation')) + navigationText = await navigationElement.getText() + assert.equal(navigationText.includes('3'), true, 'correct transaction in focus') + }) + + it('confirms a transaction', async () => { + const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`), 10000) + await confirmButton.click() + await delay(regularDelayMs) + + const navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation')) + const navigationText = await navigationElement.getText() + assert.equal(navigationText.includes('4'), true, 'transaction confirmed') + }) + + it('rejects a transaction', async () => { + const rejectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Reject')]`), 10000) + await rejectButton.click() + await delay(regularDelayMs) + + const navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation')) + const navigationText = await navigationElement.getText() + assert.equal(navigationText.includes('3'), true, 'transaction rejected') + }) + + it('rejects the rest of the transactions', async () => { + const rejectAllButton = await findElement(driver, By.xpath(`//a[contains(text(), 'Reject 3')]`), 10000) + await rejectAllButton.click() + await delay(regularDelayMs) + + const rejectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Reject All')]`), 10000) + await rejectButton.click() + await delay(largeDelayMs * 2) + + const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) + assert.equal(confirmedTxes.length, 3, '3 transactions present') + }) + }) + describe('Deploy contract and call contract methods', () => { let extension let dapp @@ -542,7 +667,7 @@ describe('MetaMask', function () { driver.wait(async () => { const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) - return confirmedTxes.length === 3 + return confirmedTxes.length === 4 }, 10000) const txAction = await findElements(driver, By.css('.transaction-list-item__action')) @@ -599,7 +724,7 @@ describe('MetaMask', function () { driver.wait(async () => { const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) - return confirmedTxes.length === 4 + return confirmedTxes.length === 5 }, 10000) const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary')) @@ -631,7 +756,7 @@ describe('MetaMask', function () { driver.wait(async () => { const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) - return confirmedTxes.length === 5 + return confirmedTxes.length === 6 }, 10000) const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary')) @@ -645,9 +770,9 @@ describe('MetaMask', function () { const balance = await findElement(driver, By.css('.transaction-view-balance__primary-balance')) await delay(regularDelayMs) if (process.env.SELENIUM_BROWSER !== 'firefox') { - await driver.wait(until.elementTextMatches(balance, /^92.*\s*ETH.*$/), 10000) + await driver.wait(until.elementTextMatches(balance, /^89.*\s*ETH.*$/), 10000) const tokenAmount = await balance.getText() - assert.ok(/^92.*\s*ETH.*$/.test(tokenAmount)) + assert.ok(/^89.*\s*ETH.*$/.test(tokenAmount)) await delay(regularDelayMs) } }) diff --git a/test/e2e/beta/run-all.sh b/test/e2e/beta/run-all.sh index c51f19fdf..f2705da4c 100755 --- a/test/e2e/beta/run-all.sh +++ b/test/e2e/beta/run-all.sh @@ -7,4 +7,5 @@ set -o pipefail export PATH="$PATH:./node_modules/.bin" shell-parallel -s 'npm run ganache:start -- -b 2' -x 'sleep 5 && static-server test/e2e/beta/contract-test --port 8080' -x 'sleep 5 && mocha test/e2e/beta/metamask-beta-ui.spec' +shell-parallel -s 'npm run ganache:start -- -b 2' -x 'sleep 5 && static-server test/e2e/beta/contract-test --port 8080' -x 'sleep 5 && mocha test/e2e/beta/metamask-beta-responsive-ui.spec' shell-parallel -s 'npm run ganache:start -- -d -b 2' -x 'sleep 5 && mocha test/e2e/beta/from-import-beta-ui.spec' diff --git a/test/e2e/func.js b/test/e2e/func.js index 13dfb82f9..5301d78ae 100644 --- a/test/e2e/func.js +++ b/test/e2e/func.js @@ -56,23 +56,31 @@ async function setupBrowserAndExtension ({ browser, extPath }) { return { driver, extensionId, extensionUri } } -function buildChromeWebDriver (extPath) { +function buildChromeWebDriver (extPath, opts = {}) { const tmpProfile = fs.mkdtempSync(path.join(os.tmpdir(), 'mm-chrome-profile')) + const args = [ + `load-extension=${extPath}`, + `user-data-dir=${tmpProfile}`, + ] + if (opts.responsive) { + args.push('--auto-open-devtools-for-tabs') + } return new webdriver.Builder() .withCapabilities({ chromeOptions: { - args: [ - `load-extension=${extPath}`, - `user-data-dir=${tmpProfile}`, - ], + args, binary: process.env.SELENIUM_CHROME_BINARY, }, }) .build() } -function buildFirefoxWebdriver () { - return new webdriver.Builder().build() +function buildFirefoxWebdriver (opts = {}) { + const driver = new webdriver.Builder().build() + if (opts.responsive) { + driver.manage().window().setSize(320, 600) + } + return driver } async function getExtensionIdChrome (driver) { diff --git a/test/integration/lib/navigate-txs.js b/test/integration/lib/navigate-txs.js new file mode 100644 index 000000000..0679d6b00 --- /dev/null +++ b/test/integration/lib/navigate-txs.js @@ -0,0 +1,87 @@ +const reactTriggerChange = require('react-trigger-change') +const { + timeout, + queryAsync, +} = require('../../lib/util') + +QUnit.module('navigate txs') + +QUnit.test('successful navigate', (assert) => { + const done = assert.async() + runNavigateTxsFlowTest(assert) + .then(done) + .catch(err => { + assert.notOk(err, `Error was thrown: ${err.stack}`) + done() + }) +}) + +async function runNavigateTxsFlowTest (assert, done) { + const selectState = await queryAsync($, 'select') + + selectState.val('navigate txs') + reactTriggerChange(selectState[0]) + + // Confirm navigation buttons present + let navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow') + assert.ok(navigateTxButtons[0], 'navigation button present') + assert.ok(navigateTxButtons[1], 'navigation button present') + assert.ok(navigateTxButtons[2], 'navigation button present') + assert.ok(navigateTxButtons[3], 'navigation button present') + + // Verify number of transactions present + let trxNum = await queryAsync($, '.confirm-page-container-navigation') + assert.equal(trxNum[0].innerText.includes('1'), true, 'starts on first') + + // Verify correct route + let summaryAction = await queryAsync($, '.confirm-page-container-summary__action') + assert.equal(summaryAction[0].innerText, 'CONTRACT DEPLOYMENT', 'correct route') + + // Click navigation button + navigateTxButtons[2].click() + await timeout(2000) + + // Verify transaction changed to num 2 and routed correctly + trxNum = await queryAsync($, '.confirm-page-container-navigation') + assert.equal(trxNum[0].innerText.includes('2'), true, 'changed transaction right') + summaryAction = await queryAsync($, '.confirm-page-container-summary__action') + // assert.equal(summaryAction[0].innerText, 'CONFIRM', 'correct route') + + // Click navigation button + navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow') + navigateTxButtons[2].click() + + // Verify transation changed to num 3 and routed correctly + trxNum = await queryAsync($, '.confirm-page-container-navigation') + assert.equal(trxNum[0].innerText.includes('3'), true, 'changed transaction right') + summaryAction = await queryAsync($, '.confirm-page-container-summary__action') + assert.equal(summaryAction[0].innerText, 'CONFIRM', 'correct route') + + // Click navigation button + navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow') + navigateTxButtons[2].click() + + // Verify transation changed to num 4 and routed correctly + trxNum = await queryAsync($, '.confirm-page-container-navigation') + assert.equal(trxNum[0].innerText.split('4').length, 3, '4 transactions present') + summaryAction = await queryAsync($, '.confirm-page-container-summary__action') + assert.equal(summaryAction[0].innerText, 'TRANSFER', 'correct route') + + // Verify left arrow is working correctly + navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow') + navigateTxButtons[1].click() + trxNum = await queryAsync($, '.confirm-page-container-navigation') + assert.equal(trxNum[0].innerText.includes('3'), true, 'changed transaction left') + + // Verify navigate to last transaction is working correctly + navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow') + navigateTxButtons[3].click() + trxNum = await queryAsync($, '.confirm-page-container-navigation') + assert.equal(trxNum[0].innerText.split('4').length, 3, 'navigate to last transaction') + + // Verify navigate to first transaction is working correctly + navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow') + navigateTxButtons[0].click() + trxNum = await queryAsync($, '.confirm-page-container-navigation') + assert.equal(trxNum[0].innerText.includes('1'), true, 'navigate to first transaction') +} diff --git a/test/unit/app/controllers/token-rates-controller.js b/test/unit/app/controllers/token-rates-controller.js index 28e583d8d..ccc279cbe 100644 --- a/test/unit/app/controllers/token-rates-controller.js +++ b/test/unit/app/controllers/token-rates-controller.js @@ -17,13 +17,4 @@ describe('TokenRatesController', () => { assert.strictEqual(stub.getCall(0).args[1], 1337) stub.restore() }) - - it('should fetch each token rate based on address', async () => { - const controller = new TokenRatesController() - controller.isActive = true - controller.fetchExchangeRate = address => address - controller.tokens = [{ address: 'foo' }, { address: 'bar' }] - await controller.updateExchangeRates() - assert.deepEqual(controller.store.getState().contractExchangeRates, { foo: 'foo', bar: 'bar' }) - }) }) diff --git a/test/unit/ui/app/reducers/app.spec.js b/test/unit/ui/app/reducers/app.spec.js new file mode 100644 index 000000000..bee4963e5 --- /dev/null +++ b/test/unit/ui/app/reducers/app.spec.js @@ -0,0 +1,998 @@ +import assert from 'assert' +import reduceApp from '../../../../../ui/app/reducers/app' +import * as actions from '../../../../../ui/app/actions' + +describe('App State', () => { + + const metamaskState = { + metamask: { + selectedAddress: '0xAddress', + identities: { + '0xAddress': { + name: 'account 1', + address: '0xAddress', + }, + }, + }, + } + + it('App init state', () => { + const initState = reduceApp(metamaskState, {}) + + assert(initState) + }) + + it('sets networkd dropdown to true', () => { + const state = reduceApp(metamaskState, { + type: actions.NETWORK_DROPDOWN_OPEN, + }) + + assert.equal(state.networkDropdownOpen, true) + }) + + it('sets networkd dropdown to false', () => { + const dropdown = { networkDropdowopen: true } + const state = {...metamaskState, ...dropdown} + const newState = reduceApp(state, { + type: actions.NETWORK_DROPDOWN_CLOSE, + }) + + assert.equal(newState.networkDropdownOpen, false) + }) + + it('opens sidebar', () => { + const value = { + 'transitionName': 'sidebar-right', + 'type': 'wallet-view', + 'isOpen': true, + } + const state = reduceApp(metamaskState, { + type: actions.SIDEBAR_OPEN, + value, + }) + + assert.deepEqual(state.sidebar, value) + }) + + it('closes sidebar', () => { + const openSidebar = { sidebar: { isOpen: true }} + const state = {...metamaskState, ...openSidebar} + + const newState = reduceApp(state, { + type: actions.SIDEBAR_CLOSE, + }) + + assert.equal(newState.sidebar.isOpen, false) + }) + + it('opens alert', () => { + const state = reduceApp(metamaskState, { + type: actions.ALERT_OPEN, + value: 'test message', + }) + + assert.equal(state.alertOpen, true) + assert.equal(state.alertMessage, 'test message') + }) + + it('closes alert', () => { + const alert = { alertOpen: true, alertMessage: 'test message' } + const state = {...metamaskState, ...alert} + const newState = reduceApp(state, { + type: actions.ALERT_CLOSE, + }) + + assert.equal(newState.alertOpen, false) + assert.equal(newState.alertMessage, null) + }) + + it('detects qr code data', () => { + const state = reduceApp(metamaskState, { + type: actions.QR_CODE_DETECTED, + value: 'qr data', + }) + + assert.equal(state.qrCodeData, 'qr data') + }) + + it('opens modal', () => { + const state = reduceApp(metamaskState, { + type: actions.MODAL_OPEN, + payload: { + name: 'test', + }, + }) + + assert.equal(state.modal.open, true) + assert.equal(state.modal.modalState.name, 'test') + }) + + it('closes modal, but moves open modal state to previous modal state', () => { + const opensModal = { + modal: { + open: true, + modalState: { + name: 'test', + }, + }, + } + + const state = { ...metamaskState, appState: { ...opensModal } } + const newState = reduceApp(state, { + type: actions.MODAL_CLOSE, + }) + + + assert.equal(newState.modal.open, false) + assert.equal(newState.modal.modalState.name, null) + }) + + it('tansitions forwards', () => { + const state = reduceApp(metamaskState, { + type: actions.TRANSITION_FORWARD, + }) + + assert.equal(state.transForward, true) + }) + + it('transition backwards', () => { + const transitionForwardState = { transitionForward: true } + + const state = { ...metamaskState, ...transitionForwardState } + const newState = reduceApp(state, { + type: actions.TRANSITION_BACKWARD, + }) + + assert.equal(newState.transForward, false) + }) + + it('shows create vault', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_CREATE_VAULT, + }) + + assert.equal(state.currentView.name, 'createVault') + assert.equal(state.transForward, true) + assert.equal(state.warning, null) + }) + + it('shows restore vault', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_RESTORE_VAULT, + }) + + assert.equal(state.currentView.name, 'restoreVault') + assert.equal(state.transForward, true) + assert.equal(state.forgottenPassword, true) + }) + + it('sets forgot password', () => { + const state = reduceApp(metamaskState, { + type: actions.FORGOT_PASSWORD, + value: true, + }) + + assert.equal(state.currentView.name, 'restoreVault') + }) + + it('shows init menu', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_INIT_MENU, + }) + + assert.equal(state.currentView.name, 'accountDetail') + assert.equal(state.currentView.context, '0xAddress') + }) + + it('shows config page', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_CONFIG_PAGE, + value: true, + }) + + assert.equal(state.currentView.name, 'config') + assert.equal(state.currentView.context, '0xAddress') + assert.equal(state.transForward, true) + }) + + it('shows add token page', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_ADD_TOKEN_PAGE, + value: true, + }) + + assert.equal(state.currentView.name, 'add-token') + assert.equal(state.currentView.context, '0xAddress') + assert.equal(state.transForward, true) + }) + + it('shows add suggested token page', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_ADD_SUGGESTED_TOKEN_PAGE, + value: true, + }) + + assert.equal(state.currentView.name, 'add-suggested-token') + assert.equal(state.currentView.context, '0xAddress') + assert.equal(state.transForward, true) + }) + + it('shows import page', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_IMPORT_PAGE, + }) + + assert.equal(state.currentView.name, 'import-menu') + assert.equal(state.transForward, true) + assert.equal(state.warning, null) + }) + + it('shows new account page', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_NEW_ACCOUNT_PAGE, + formToSelect: 'context', + }) + + assert.equal(state.currentView.name, 'new-account-page') + assert.equal(state.currentView.context, 'context') + assert.equal(state.transForward, true) + assert.equal(state.warning, null) + }) + + it('sets new account form', () => { + const state = reduceApp(metamaskState, { + type: actions.SET_NEW_ACCOUNT_FORM, + formToSelect: 'context', + }) + + assert.equal(state.currentView.name, 'accountDetail') + assert.equal(state.currentView.context, 'context') + }) + + it('shows info page', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_INFO_PAGE, + }) + + assert.equal(state.currentView.name, 'info') + assert.equal(state.currentView.context, '0xAddress') + assert.equal(state.transForward, true) + }) + + it('creates new vault in progress', () => { + const state = reduceApp(metamaskState, { + type: actions.CREATE_NEW_VAULT_IN_PROGRESS, + }) + + assert.equal(state.currentView.name, 'createVault') + assert.equal(state.currentView.inProgress, true) + assert.equal(state.transForward, true) + assert.equal(state.isLoading, true) + }) + + it('shows new vault seed', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_NEW_VAULT_SEED, + value: 'test seed words', + }) + + assert.equal(state.currentView.name, 'createVaultComplete') + assert.equal(state.currentView.seedWords, 'test seed words') + assert.equal(state.transForward, true) + assert.equal(state.isLoading, false) + }) + + it('shows new account screen', () => { + const state = reduceApp(metamaskState, { + type: actions.NEW_ACCOUNT_SCREEN, + }) + + assert.equal(state.currentView.name, 'new-account') + assert.equal(state.currentView.context, '0xAddress') + assert.equal(state.transForward, true) + }) + + it('shows send page', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_SEND_PAGE, + }) + + assert.equal(state.currentView.name, 'sendTransaction') + assert.equal(state.currentView.context, '0xAddress') + assert.equal(state.transForward, true) + assert.equal(state.warning, null) + }) + + it('shows send token page', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_SEND_TOKEN_PAGE, + }) + + assert.equal(state.currentView.name, 'sendToken') + assert.equal(state.currentView.context, '0xAddress') + assert.equal(state.transForward, true) + assert.equal(state.warning, null) + }) + + it('shows new keychain', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_NEW_KEYCHAIN, + }) + + assert.equal(state.currentView.name, 'newKeychain') + assert.equal(state.currentView.context, '0xAddress') + assert.equal(state.transForward, true) + }) + + it('unlocks Metamask', () => { + const state = reduceApp(metamaskState, { + type: actions.UNLOCK_METAMASK, + }) + + assert.equal(state.forgottenPassword, null) + assert.deepEqual(state.detailView, {}) + assert.equal(state.transForward, true) + assert.equal(state.warning, null) + }) + + it('locks Metamask', () => { + const state = reduceApp(metamaskState, { + type: actions.LOCK_METAMASK, + }) + + assert.equal(state.currentView.name, 'accountDetail') + assert.equal(state.currentView.context, '0xAddress') + assert.equal(state.transForward, false) + assert.equal(state.warning, null) + }) + + it('goes back to init menu', () => { + const state = reduceApp(metamaskState, { + type: actions.BACK_TO_INIT_MENU, + }) + + assert.equal(state.currentView.name, 'InitMenu') + assert.equal(state.transForward, false) + assert.equal(state.warning, null) + assert.equal(state.forgottenPassword, true) + }) + + it('goes back to unlock view', () => { + const state = reduceApp(metamaskState, { + type: actions.BACK_TO_UNLOCK_VIEW, + }) + + assert.equal(state.currentView.name, 'UnlockScreen') + assert.equal(state.transForward, true) + assert.equal(state.warning, null) + assert.equal(state.forgottenPassword, false) + }) + + it('reveals seed words', () => { + const state = reduceApp(metamaskState, { + type: actions.REVEAL_SEED_CONFIRMATION, + }) + + assert.equal(state.currentView.name, 'reveal-seed-conf') + assert.equal(state.transForward, true) + assert.equal(state.warning, null) + }) + + it('sets selected account', () => { + const state = reduceApp(metamaskState, { + type: actions.SET_SELECTED_ACCOUNT, + value: 'active address', + }) + + assert.equal(state.activeAddress, 'active address') + }) + + it('goes home', () => { + const state = reduceApp(metamaskState, { + type: actions.GO_HOME, + }) + + assert.equal(state.currentView.name, 'accountDetail') + assert.equal(state.accountDetail.subview, 'transactions') + assert.equal(state.accountDetail.accountExport, 'none') + assert.equal(state.accountDetail.privateKey, '') + assert.equal(state.transForward, false) + assert.equal(state.warning, null) + + }) + + it('shows account detail', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_ACCOUNT_DETAIL, + value: 'context address', + }) + assert.equal(state.forgottenPassword, null) // default + assert.equal(state.currentView.name, 'accountDetail') + assert.equal(state.currentView.context, 'context address') + assert.equal(state.accountDetail.subview, 'transactions') // default + assert.equal(state.accountDetail.accountExport, 'none') // default + assert.equal(state.accountDetail.privateKey, '') // default + assert.equal(state.transForward, false) + + }) + + it('goes back to account detail', () => { + const state = reduceApp(metamaskState, { + type: actions.BACK_TO_ACCOUNT_DETAIL, + value: 'context address', + }) + assert.equal(state.forgottenPassword, null) // default + assert.equal(state.currentView.name, 'accountDetail') + assert.equal(state.currentView.context, 'context address') + assert.equal(state.accountDetail.subview, 'transactions') // default + assert.equal(state.accountDetail.accountExport, 'none') // default + assert.equal(state.accountDetail.privateKey, '') // default + assert.equal(state.transForward, false) + + }) + + it('shoes account page', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_ACCOUNTS_PAGE, + }) + + assert.equal(state.currentView.name, 'accounts') + assert.equal(state.currentView.seedWords, undefined) + assert.equal(state.transForward, true) + assert.equal(state.isLoading, false) + assert.equal(state.warning, null) + assert.equal(state.scrollToBottom, false) + assert.equal(state.forgottenPassword, false) + }) + + it('shows notice', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_NOTICE, + }) + + assert.equal(state.transForward, true) + assert.equal(state.isLoading, false) + }) + + it('reveals account', () => { + const state = reduceApp(metamaskState, { + type: actions.REVEAL_ACCOUNT, + }) + assert.equal(state.scrollToBottom, true) + }) + + it('shows confirm tx page', () => { + const txs = { + unapprovedTxs: { + 1: { + id: 1, + }, + 2: { + id: 2, + }, + }, + } + const oldState = { + metamask: {...metamaskState.metamask, ...txs}, + } + const state = reduceApp(oldState, { + type: actions.SHOW_CONF_TX_PAGE, + id: 2, + transForward: false, + }) + + assert.equal(state.currentView.name, 'confTx') + assert.equal(state.currentView.context, 1) + assert.equal(state.transForward, false) + assert.equal(state.warning, null) + assert.equal(state.isLoading, false) + + }) + + it('shows confirm msg page', () => { + const msgs = { + unapprovedMsgs: { + 1: { + id: 1, + }, + 2: { + id: 2, + }, + }, + } + + const oldState = { + metamask: {...metamaskState, ...msgs}, + } + + const state = reduceApp(oldState, { + type: actions.SHOW_CONF_MSG_PAGE, + }) + + assert.equal(state.currentView.name, 'confTx') + assert.equal(state.currentView.context, 0) + assert.equal(state.transForward, true) + assert.equal(state.warning, null) + assert.equal(state.isLoading, false) + + }) + + it('completes tx continues to show pending txs current view context', () => { + const txs = { + unapprovedTxs: { + 1: { + id: 1, + }, + 2: { + id: 2, + }, + }, + } + + const oldState = { + metamask: {...metamaskState, ...txs}, + } + + const state = reduceApp(oldState, { + type: actions.COMPLETED_TX, + value: 1, + }) + + assert.equal(state.currentView.name, 'confTx') + assert.equal(state.currentView.context, 0) + assert.equal(state.transForward, false) + assert.equal(state.warning, null) + }) + + it('returns to account detail page when no unconf actions completed tx', () => { + const state = reduceApp(metamaskState, { + type: actions.COMPLETED_TX, + }) + + assert.equal(state.currentView.name, 'accountDetail') + assert.equal(state.currentView.context, '0xAddress') + assert.equal(state.transForward, false) + assert.equal(state.warning, null) + assert.equal(state.accountDetail.subview, 'transactions') + + }) + + it('proceeds to change current view context in confTx', () => { + + const oldState = { + metamask: {metamaskState}, + appState: {currentView: {context: 0}}, + } + + const state = reduceApp(oldState, { + type: actions.NEXT_TX, + }) + + assert.equal(state.currentView.name, 'confTx') + assert.equal(state.currentView.context, 1) + assert.equal(state.warning, null) + }) + + it('views pending tx', () => { + const txs = { + unapprovedTxs: { + 1: { + id: 1, + }, + 2: { + id: 2, + }, + }, + } + + + const oldState = { + metamask: {...metamaskState, ...txs}, + } + + const state = reduceApp(oldState, { + type: actions.VIEW_PENDING_TX, + value: 2, + }) + + assert.equal(state.currentView.name, 'confTx') + assert.equal(state.currentView.context, 1) + assert.equal(state.warning, null) + }) + + it('views previous tx', () => { + const txs = { + unapprovedTxs: { + 1: { + id: 1, + }, + 2: { + id: 2, + }, + }, + } + + + const oldState = { + metamask: {...metamaskState, ...txs}, + } + + const state = reduceApp(oldState, { + type: actions.VIEW_PENDING_TX, + value: 2, + }) + + assert.equal(state.currentView.name, 'confTx') + assert.equal(state.currentView.context, 1) + assert.equal(state.warning, null) + }) + + it('sets error message in confTx view', () => { + const state = reduceApp(metamaskState, { + type: actions.TRANSACTION_ERROR, + }) + + assert.equal(state.currentView.name, 'confTx') + assert.equal(state.currentView.errorMessage, 'There was a problem submitting this transaction.') + }) + + it('sets default warning when unlock fails', () => { + const state = reduceApp(metamaskState, { + type: actions.UNLOCK_FAILED, + }) + + assert.equal(state.warning, 'Incorrect password. Try again.') + }) + + it('sets default warning when unlock fails', () => { + const state = reduceApp(metamaskState, { + type: actions.UNLOCK_FAILED, + value: 'errors', + }) + + assert.equal(state.warning, 'errors') + }) + + it('sets warning to empty string when unlock succeeds', () => { + const errorState = { warning: 'errors' } + const oldState = {...metamaskState, ...errorState} + const state = reduceApp(oldState, { + type: actions.UNLOCK_SUCCEEDED, + }) + + assert.equal(state.warning, '') + }) + + it('sets hardware wallet default hd path', () => { + const hdPaths = { + trezor: "m/44'/60'/0'/0", + ledger: "m/44'/60'/0'", + } + const state = reduceApp(metamaskState, { + type: actions.SET_HARDWARE_WALLET_DEFAULT_HD_PATH, + value: { + device: 'ledger', + path: "m/44'/60'/0'", + }, + }) + + assert.deepEqual(state.defaultHdPaths, hdPaths) + }) + + it('shows loading message', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_LOADING, + value: 'loading', + }) + + assert.equal(state.isLoading, true) + assert.equal(state.loadingMessage, 'loading') + }) + + it('hides loading message', () => { + const loadingState = { isLoading: true} + const oldState = {...metamaskState, ...loadingState} + + const state = reduceApp(oldState, { + type: actions.HIDE_LOADING, + }) + + assert.equal(state.isLoading, false) + }) + + it('shows sub loading indicator', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_SUB_LOADING_INDICATION, + }) + + assert.equal(state.isSubLoading, true) + }) + + it('hides sub loading indicator', () => { + const oldState = {...metamaskState, ...oldState} + const state = reduceApp(oldState, { + type: actions.HIDE_SUB_LOADING_INDICATION, + }) + + assert.equal(state.isSubLoading, false) + }) + + it('displays warning', () => { + const state = reduceApp(metamaskState, { + type: actions.DISPLAY_WARNING, + value: 'warning', + }) + + assert.equal(state.isLoading, false) + assert.equal(state.warning, 'warning') + }) + + it('hides warning', () => { + const displayWarningState = { warning: 'warning'} + const oldState = {...metamaskState, ...displayWarningState} + const state = reduceApp(oldState, { + type: actions.HIDE_WARNING, + }) + + assert.equal(state.warning, undefined) + }) + + it('request to display account export', () => { + const state = reduceApp(metamaskState, { + type: actions.REQUEST_ACCOUNT_EXPORT, + }) + + assert.equal(state.transForward, true) + assert.equal(state.accountDetail.subview, 'export') + assert.equal(state.accountDetail.accountExport, 'requested') + }) + + it('completes account export', () => { + const requestAccountExportState = { + accountDetail: { + subview: 'something', + accountExport: 'progress', + }, + } + const oldState = {...metamaskState, ...requestAccountExportState} + const state = reduceApp(oldState, { + type: actions.EXPORT_ACCOUNT, + }) + + assert.equal(state.accountDetail.subview, 'export') + assert.equal(state.accountDetail.accountExport, 'completed') + }) + + it('shows private key', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_PRIVATE_KEY, + value: 'private key', + }) + + assert.equal(state.accountDetail.subview, 'export') + assert.equal(state.accountDetail.accountExport, 'completed') + assert.equal(state.accountDetail.privateKey, 'private key') + }) + + it('shows buy eth view', () => { + + const state = reduceApp(metamaskState, { + type: actions.BUY_ETH_VIEW, + value: '0xAddress', + }) + + assert.equal(state.currentView.name, 'buyEth') + assert.equal(state.currentView.context, 'accountDetail') + assert.equal(state.identity.address, '0xAddress') + assert.equal(state.buyView.subview, 'Coinbase') + assert.equal(state.buyView.amount, '15.00') + assert.equal(state.buyView.buyAddress, '0xAddress') + assert.equal(state.buyView.formView.coinbase, true) + assert.equal(state.buyView.formView.shapeshift, false) + }) + + it('shows onboarding subview to buy eth', () => { + const state = reduceApp(metamaskState, { + type: actions.ONBOARDING_BUY_ETH_VIEW, + value: '0xAddress', + }) + + assert.equal(state.currentView.name, 'onboardingBuyEth') + assert.equal(state.currentView.context, 'accountDetail') + assert.equal(state.identity.address, '0xAddress') + }) + + it('shows coinbase subview', () => { + const appState = { + appState: { + buyView: { + buyAddress: '0xAddress', + amount: '12.00', + }, + }, + } + const oldState = {...metamaskState, ...appState} + const state = reduceApp(oldState, { + type: actions.COINBASE_SUBVIEW, + }) + + assert.equal(state.buyView.subview, 'Coinbase') + assert.equal(state.buyView.formView.coinbase, true) + assert.equal(state.buyView.buyAddress, '0xAddress') + assert.equal(state.buyView.amount, '12.00') + }) + + it('shows shapeshift subview', () => { + const appState = { + appState: { + buyView: { + buyAddress: '0xAddress', + amount: '12.00', + }, + }, + } + + const marketinfo = { + pair: 'BTC_ETH', + rate: 28.91191106, + minerFee: 0.0022, + limit: 0.76617432, + minimum: 0.00015323, + maxLimit: 0.76617432, + } + + const coinOptions = { + BTC: { + symbol: 'BTC', + name: 'Bitcoin', + image: 'https://shapeshift.io/images/coins/bitcoin.png', + imageSmall: 'https://shapeshift.io/images/coins-sm/bitcoin.png', + status: 'available', + minerFee: 0.00025, + }, + } + + const oldState = {...metamaskState, ...appState} + + const state = reduceApp(oldState, { + type: actions.SHAPESHIFT_SUBVIEW, + value: { + marketinfo, + coinOptions, + }, + }) + + assert.equal(state.buyView.subview, 'ShapeShift') + assert.equal(state.buyView.formView.shapeshift, true) + assert.deepEqual(state.buyView.formView.marketinfo, marketinfo) + assert.deepEqual(state.buyView.formView.coinOptions, coinOptions) + assert.equal(state.buyView.buyAddress, '0xAddress') + assert.equal(state.buyView.amount, '12.00') + }) + + it('updates pair', () => { + const coinOptions = { + BTC: { + symbol: 'BTC', + name: 'Bitcoin', + image: 'https://shapeshift.io/images/coins/bitcoin.png', + imageSmall: 'https://shapeshift.io/images/coins-sm/bitcoin.png', + status: 'available', + minerFee: 0.00025, + }, + } + + const appState = { + appState: { + buyView: { + buyAddress: '0xAddress', + amount: '12.00', + formView: { + coinOptions, + }, + }, + }, + } + + const marketinfo = { + pair: 'BTC_ETH', + rate: 28.91191106, + minerFee: 0.0022, + limit: 0.76617432, + minimum: 0.00015323, + maxLimit: 0.76617432, + } + + const oldState = {...metamaskState, ...appState} + + const state = reduceApp(oldState, { + type: actions.PAIR_UPDATE, + value: { + marketinfo, + }, + }) + + assert.equal(state.buyView.subview, 'ShapeShift') + assert.equal(state.buyView.formView.shapeshift, true) + assert.deepEqual(state.buyView.formView.marketinfo, marketinfo) + assert.deepEqual(state.buyView.formView.coinOptions, coinOptions) + assert.equal(state.buyView.buyAddress, '0xAddress') + assert.equal(state.buyView.amount, '12.00') + }) + + it('shows QR', () => { + const state = reduceApp(metamaskState, { + type: actions.SHOW_QR, + value: { + message: 'message', + data: 'data', + }, + }) + + assert.equal(state.qrRequested, true) + assert.equal(state.transForward, true) + assert.equal(state.Qr.message, 'message') + assert.equal(state.Qr.data, 'data') + }) + + it('shows qr view', () => { + const appState = { + appState: { + currentView: { + context: 'accounts', + }, + }, + } + + const oldState = {...metamaskState, ...appState} + const state = reduceApp(oldState, { + type: actions.SHOW_QR_VIEW, + value: { + message: 'message', + data: 'data', + }, + }) + + assert.equal(state.currentView.name, 'qr') + assert.equal(state.currentView.context, 'accounts') + assert.equal(state.transForward, true) + assert.equal(state.Qr.message, 'message') + assert.equal(state.Qr.data, 'data') + }) + + it('set mouse user state', () => { + const state = reduceApp(metamaskState, { + type: actions.SET_MOUSE_USER_STATE, + value: true, + }) + + assert.equal(state.isMouseUser, true) + }) + + it('sets gas loading', () => { + const state = reduceApp(metamaskState, { + type: actions.GAS_LOADING_STARTED, + }) + + assert.equal(state.gasIsLoading, true) + }) + + it('unsets gas loading', () => { + const gasLoadingState = { gasIsLoading: true } + const oldState = {...metamaskState, ...gasLoadingState} + const state = reduceApp(oldState, { + type: actions.GAS_LOADING_FINISHED, + }) + + assert.equal(state.gasIsLoading, false) + }) + + it('sets network nonce', () => { + const state = reduceApp(metamaskState, { + type: actions.SET_NETWORK_NONCE, + value: '33', + }) + + assert.equal(state.networkNonce, '33') + }) +}) diff --git a/test/unit/ui/app/reducers/metamask.spec.js b/test/unit/ui/app/reducers/metamask.spec.js new file mode 100644 index 000000000..e1a50eef2 --- /dev/null +++ b/test/unit/ui/app/reducers/metamask.spec.js @@ -0,0 +1,576 @@ +import assert from 'assert' +import reduceMetamask from '../../../../../ui/app/reducers/metamask' +import * as actions from '../../../../../ui/app/actions' + +describe('MetaMask Reducers', () => { + + it('init state', () => { + const initState = reduceMetamask({metamask:{}}, {}) + assert(initState) + }) + + it('sets revealing seed to true and adds seed words to new state', () => { + const seedWordsState = reduceMetamask({}, { + type: actions.SHOW_NEW_VAULT_SEED, + value: 'test seed words', + }) + + assert.equal(seedWordsState.seedWords, 'test seed words') + assert.equal(seedWordsState.isRevealingSeedWords, true) + }) + + it('shows account page', () => { + const seedWordsState = { + metamask: { + seedwords: 'test seed words', + isRevealing: true, + }, + } + + const state = reduceMetamask(seedWordsState, { + type: actions.SHOW_ACCOUNTS_PAGE, + }) + + assert.equal(state.seedWords, undefined) + assert.equal(state.isRevealingSeedWords, false) + }) + + it('shows notice', () => { + const notice = { + id: 0, + read: false, + date: 'Date', + title: 'Title', + body: 'Body', + } + + const state = reduceMetamask({}, { + type: actions.SHOW_NOTICE, + value: notice, + }) + + assert.equal(state.noActiveNotices, false) + assert.equal(state.nextUnreadNotice, notice) + }) + + it('clears notice', () => { + + const notice = { + id: 0, + read: false, + date: 'Date', + title: 'Title', + body: 'Body', + } + + const noticesState = { + metamask: { + noActiveNotices: false, + nextUnreadNotice: notice, + }, + } + + const state = reduceMetamask(noticesState, { + type: actions.CLEAR_NOTICES, + }) + + assert.equal(state.noActiveNotices, true) + assert.equal(state.nextUnreadNotice, null) + }) + + it('unlocks MetaMask', () => { + const state = reduceMetamask({}, { + type: actions.UNLOCK_METAMASK, + value: 'test address', + }) + + assert.equal(state.isUnlocked, true) + assert.equal(state.isInitialized, true) + assert.equal(state.selectedAddress, 'test address') + }) + + it('locks MetaMask', () => { + const unlockMetaMaskState = { + metamask: { + isUnlocked: true, + isInitialzed: false, + selectedAddress: 'test address', + }, + } + const lockMetaMask = reduceMetamask(unlockMetaMaskState, { + type: actions.LOCK_METAMASK, + }) + + assert.equal(lockMetaMask.isUnlocked, false) + }) + + it('sets frequent rpc list', () => { + const state = reduceMetamask({}, { + type: actions.SET_RPC_LIST, + value: 'https://custom.rpc', + }) + + assert.equal(state.frequentRpcList, 'https://custom.rpc') + }) + + it('sets rpc target', () => { + const state = reduceMetamask({}, { + type: actions.SET_RPC_TARGET, + value: 'https://custom.rpc', + }) + + assert.equal(state.provider.rpcTarget, 'https://custom.rpc') + }) + + it('sets provider type', () => { + const state = reduceMetamask({}, { + type: actions.SET_PROVIDER_TYPE, + value: 'provider type', + }) + + assert.equal(state.provider.type, 'provider type') + }) + + describe('CompletedTx', () => { + const oldState = { + metamask: { + unapprovedTxs: { + 1: { + id: 1, + time: 1538495996507, + status: 'unapproved', + metamaskNetworkId: 4, + loadingDefaults: false, + txParams: { + from: '0xAddress', + to: '0xAddress2', + value: '0x16345785d8a0000', + gas: '0x5208', + gasPrice: '0x3b9aca00', + }, + type: 'standard', + }, + 2: { + test: 'Should persist', + }, + }, + unapprovedMsgs: { + 1: { + id: 2, + msgParams: { + from: '0xAddress', + data: '0xData', + origin: 'test origin', + }, + time: 1538498521717, + status: 'unapproved', + type: 'eth_sign', + }, + 2: { + test: 'Should Persist', + }, + }, + }, + } + + it('removes tx from new state if completed in action.', () => { + + const state = reduceMetamask(oldState, { + type: actions.COMPLETED_TX, + id: 1, + }) + + assert.equal(Object.keys(state.unapprovedTxs).length, 1) + assert.equal(state.unapprovedTxs[2].test, 'Should persist') + }) + + it('removes msg from new state if completed id in action', () => { + const state = reduceMetamask(oldState, { + type: actions.COMPLETED_TX, + id: 1, + }) + + assert.equal(Object.keys(state.unapprovedMsgs).length, 1) + assert.equal(state.unapprovedTxs[2].test, 'Should persist') + }) + }) + + it('shows new vault seed words and sets isRevealingSeedWords to true', () => { + const showNewVaultSeedState = reduceMetamask({}, { + type: actions.SHOW_NEW_VAULT_SEED, + value: 'test seed words', + }) + + assert.equal(showNewVaultSeedState.isRevealingSeedWords, true) + assert.equal(showNewVaultSeedState.seedWords, 'test seed words') + }) + + it('shows account detail', () => { + + const state = reduceMetamask({}, { + type: actions.SHOW_ACCOUNT_DETAIL, + value: 'test address', + }) + + assert.equal(state.isUnlocked, true) + assert.equal(state.isInitialized, true) + assert.equal(state.selectedAddress, 'test address') + }) + + it('sets select ', () => { + const state = reduceMetamask({}, { + type: actions.SET_SELECTED_TOKEN, + value: 'test token', + }) + + assert.equal(state.selectedTokenAddress, 'test token') + }) + + it('sets account label', () => { + const state = reduceMetamask({}, { + type: actions.SET_ACCOUNT_LABEL, + value: { + account: 'test account', + label: 'test label', + }, + }) + + assert.deepEqual(state.identities, { 'test account': { name: 'test label' } }) + }) + + it('sets current fiat', () => { + const value = { + currentCurrency: 'yen', + conversionRate: 3.14, + conversionDate: new Date(2018, 9), + } + + const state = reduceMetamask({}, { + type: actions.SET_CURRENT_FIAT, + value, + }) + + assert.equal(state.currentCurrency, value.currentCurrency) + assert.equal(state.conversionRate, value.conversionRate) + assert.equal(state.conversionDate, value.conversionDate) + }) + + it('updates tokens', () => { + const newTokens = { + 'address': '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', + 'decimals': 18, + 'symbol': 'META', + } + + const state = reduceMetamask({}, { + type: actions.UPDATE_TOKENS, + newTokens, + }) + + assert.deepEqual(state.tokens, newTokens) + }) + + it('updates send gas limit', () => { + + const state = reduceMetamask({}, { + type: actions.UPDATE_GAS_LIMIT, + value: '0xGasLimit', + }) + + assert.equal(state.send.gasLimit, '0xGasLimit') + }) + + it('updates send gas price', () => { + const state = reduceMetamask({}, { + type: actions.UPDATE_GAS_PRICE, + value: '0xGasPrice', + }) + + assert.equal(state.send.gasPrice, '0xGasPrice') + }) + + it('toggles account menu ', () => { + const state = reduceMetamask({}, { + type: actions.TOGGLE_ACCOUNT_MENU, + }) + + assert.equal(state.isAccountMenuOpen, true) + }) + + it('updates gas total', () => { + const state = reduceMetamask({}, { + type: actions.UPDATE_GAS_TOTAL, + value: '0xGasTotal', + }) + + assert.equal(state.send.gasTotal, '0xGasTotal') + }) + + it('updates send token balance', () => { + const state = reduceMetamask({}, { + type: actions.UPDATE_SEND_TOKEN_BALANCE, + value: '0xTokenBalance', + }) + + assert.equal(state.send.tokenBalance, '0xTokenBalance') + }) + + it('updates data', () => { + const state = reduceMetamask({}, { + type: actions.UPDATE_SEND_HEX_DATA, + value: '0xData', + }) + + assert.equal(state.send.data, '0xData') + }) + + it('updates send to', () => { + const state = reduceMetamask({}, { + type: actions.UPDATE_SEND_TO, + value: { + to: '0xAddress', + nickname: 'nickname', + }, + }) + + assert.equal(state.send.to, '0xAddress') + assert.equal(state.send.toNickname, 'nickname') + }) + + it('update send from', () => { + const state = reduceMetamask({}, { + type: actions.UPDATE_SEND_FROM, + value: '0xAddress', + }) + + assert.equal(state.send.from, '0xAddress') + }) + + it('update send amount', () => { + const state = reduceMetamask({}, { + type: actions.UPDATE_SEND_AMOUNT, + value: '0xAmount', + }) + + assert.equal(state.send.amount, '0xAmount') + }) + + it('update send memo', () => { + const state = reduceMetamask({}, { + type: actions.UPDATE_SEND_MEMO, + value: '0xMemo', + }) + + assert.equal(state.send.memo, '0xMemo') + }) + + it('updates max mode', () => { + const state = reduceMetamask({}, { + type: actions.UPDATE_MAX_MODE, + value: true, + }) + + assert.equal(state.send.maxModeOn, true) + }) + + it('update send', () => { + const value = { + gasLimit: '0xGasLimit', + gasPrice: '0xGasPrice', + gasTotal: '0xGasTotal', + tokenBalance: '0xBalance', + from: '0xAddress', + to: '0xAddress', + toNickname: '', + maxModeOn: false, + amount: '0xAmount', + memo: '0xMemo', + errors: {}, + editingTransactionId: 22, + forceGasMin: '0xGas', + } + + const sendState = reduceMetamask({}, { + type: actions.UPDATE_SEND, + value, + }) + + assert.deepEqual(sendState.send, value) + }) + + it('clears send', () => { + const initStateSend = { + send: + { gasLimit: null, + gasPrice: null, + gasTotal: null, + tokenBalance: null, + from: '', + to: '', + amount: '0x0', + memo: '', + errors: {}, + maxModeOn: false, + editingTransactionId: null, + forceGasMin: null, + toNickname: '' }, + } + + const sendState = { + send: { + gasLimit: '0xGasLimit', + gasPrice: '0xGasPrice', + gasTotal: '0xGasTotal', + tokenBalance: '0xBalance', + from: '0xAddress', + to: '0xAddress', + toNickname: '', + maxModeOn: false, + amount: '0xAmount', + memo: '0xMemo', + errors: {}, + editingTransactionId: 22, + forceGasMin: '0xGas', + }, + } + + + const state = reduceMetamask(sendState, { + type: actions.CLEAR_SEND, + }) + + assert.deepEqual(state.send, initStateSend.send) + }) + + it('updates value of tx by id', () => { + const oldState = { + metamask: { + selectedAddressTxList: [ + { + id: 1, + txParams: 'foo', + }, + ], + }, + } + + const state = reduceMetamask(oldState, { + type: actions.UPDATE_TRANSACTION_PARAMS, + id: 1, + value: 'bar', + }) + + assert.equal(state.selectedAddressTxList[0].txParams, 'bar') + }) + + it('updates pair for shapeshift', () => { + const state = reduceMetamask({}, { + type: actions.PAIR_UPDATE, + value: { + marketinfo: { + pair: 'test pair', + foo: 'bar', + }, + }, + }) + assert.equal(state.tokenExchangeRates['test pair'].pair, 'test pair') + }) + + it('upates pair and coin options for shapeshift subview', () => { + const state = reduceMetamask({}, { + type: actions.SHAPESHIFT_SUBVIEW, + value: { + marketinfo: { + pair: 'test pair', + }, + coinOptions: { + foo: 'bar', + }, + }, + }) + + assert.equal(state.coinOptions.foo, 'bar') + assert.equal(state.tokenExchangeRates['test pair'].pair, 'test pair') + }) + + it('sets blockies', () => { + const state = reduceMetamask({}, { + type: actions.SET_USE_BLOCKIE, + value: true, + }) + + assert.equal(state.useBlockie, true) + }) + + it('updates feature flag', () => { + const state = reduceMetamask({}, { + type: actions.UPDATE_FEATURE_FLAGS, + value: { + betaUI: true, + skipAnnounceBetaUI: true, + }, + }) + + assert.equal(state.featureFlags.betaUI, true) + assert.equal(state.featureFlags.skipAnnounceBetaUI, true) + }) + + it('updates network endpoint type', () => { + const state = reduceMetamask({}, { + type: actions.UPDATE_NETWORK_ENDPOINT_TYPE, + value: 'endpoint', + }) + + assert.equal(state.networkEndpointType, 'endpoint') + }) + + it('close welcome screen', () => { + const state = reduceMetamask({}, { + type: actions.CLOSE_WELCOME_SCREEN, + }) + + assert.equal(state.welcomeScreenSeen, true) + }) + + it('sets current locale', () => { + const state = reduceMetamask({}, { + type: actions.SET_CURRENT_LOCALE, + value: 'ge', + }) + + assert.equal(state.currentLocale, 'ge') + }) + + it('sets pending tokens ', () => { + const payload = { + 'address': '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', + 'decimals': 18, + 'symbol': 'META', + } + + const pendingTokensState = reduceMetamask({}, { + type: actions.SET_PENDING_TOKENS, + payload, + }) + + assert.deepEqual(pendingTokensState.pendingTokens, payload) + }) + + it('clears pending tokens', () => { + const payload = { + 'address': '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', + 'decimals': 18, + 'symbol': 'META', + } + + const pendingTokensState = { + pendingTokens: payload, + } + + const state = reduceMetamask(pendingTokensState, { + type: actions.CLEAR_PENDING_TOKENS, + }) + + assert.deepEqual(state.pendingTokens, {}) + }) +}) diff --git a/ui/app/components/balance-component.js b/ui/app/components/balance-component.js index 0f8514c81..4e2769ee8 100644 --- a/ui/app/components/balance-component.js +++ b/ui/app/components/balance-component.js @@ -86,13 +86,13 @@ BalanceComponent.prototype.renderBalance = function () { className: 'token-amount', value: balanceValue, type: PRIMARY, - ethNumberOfDecimals: 3, + ethNumberOfDecimals: 4, }), showFiat && h(UserPreferencedCurrencyDisplay, { value: balanceValue, type: SECONDARY, - ethNumberOfDecimals: 3, + ethNumberOfDecimals: 4, }), ]) } diff --git a/ui/app/components/confirm-page-container/confirm-page-container-header/index.scss b/ui/app/components/confirm-page-container/confirm-page-container-header/index.scss index 43e1e4427..be77edbdf 100644 --- a/ui/app/components/confirm-page-container/confirm-page-container-header/index.scss +++ b/ui/app/components/confirm-page-container/confirm-page-container-header/index.scss @@ -7,7 +7,7 @@ display: flex; justify-content: space-between; border-bottom: 1px solid $geyser; - padding: 13px 13px 13px 24px; + padding: 4px 13px 4px 13px; flex: 0 0 auto; } diff --git a/ui/app/components/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js b/ui/app/components/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js new file mode 100755 index 000000000..8327f997b --- /dev/null +++ b/ui/app/components/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js @@ -0,0 +1,69 @@ +import React from 'react' +import PropTypes from 'prop-types' + +const ConfirmPageContainerNavigation = props => { + const { onNextTx, totalTx, positionOfCurrentTx, nextTxId, prevTxId, showNavigation, firstTx, lastTx, ofText, requestsWaitingText } = props + + return ( + <div className="confirm-page-container-navigation" + style={{ + display: showNavigation ? 'flex' : 'none', + }} + > + <div className="confirm-page-container-navigation__container" + style={{ + visibility: prevTxId ? 'initial' : 'hidden', + }}> + <div + className="confirm-page-container-navigation__arrow" + onClick={() => onNextTx(firstTx)}> + <img src="/images/double-arrow.svg" /> + </div> + <div + className="confirm-page-container-navigation__arrow" + onClick={() => onNextTx(prevTxId)}> + <img src="/images/single-arrow.svg" /> + </div> + </div> + <div className="confirm-page-container-navigation__textcontainer"> + <div className="confirm-page-container-navigation__navtext"> + {positionOfCurrentTx} {ofText} {totalTx} + </div> + <div className="confirm-page-container-navigation__longtext"> + {requestsWaitingText} + </div> + </div> + <div + className="confirm-page-container-navigation__container" + style={{ + visibility: nextTxId ? 'initial' : 'hidden', + }}> + <div + className="confirm-page-container-navigation__arrow" + onClick={() => onNextTx(nextTxId)}> + <img className="confirm-page-container-navigation__imageflip" src="/images/single-arrow.svg" /> + </div> + <div + className="confirm-page-container-navigation__arrow" + onClick={() => onNextTx(lastTx)}> + <img className="confirm-page-container-navigation__imageflip" src="/images/double-arrow.svg" /> + </div> + </div> + </div> + ) +} + +ConfirmPageContainerNavigation.propTypes = { + totalTx: PropTypes.number, + positionOfCurrentTx: PropTypes.number, + onNextTx: PropTypes.func, + nextTxId: PropTypes.string, + prevTxId: PropTypes.string, + showNavigation: PropTypes.bool, + firstTx: PropTypes.string, + lastTx: PropTypes.string, + ofText: PropTypes.string, + requestsWaitingText: PropTypes.string, +} + +export default ConfirmPageContainerNavigation diff --git a/ui/app/components/confirm-page-container/confirm-page-container-navigation/index.js b/ui/app/components/confirm-page-container/confirm-page-container-navigation/index.js new file mode 100755 index 000000000..d97c1b447 --- /dev/null +++ b/ui/app/components/confirm-page-container/confirm-page-container-navigation/index.js @@ -0,0 +1 @@ +export { default } from './confirm-page-container-navigation.component' diff --git a/ui/app/components/confirm-page-container/confirm-page-container-navigation/index.scss b/ui/app/components/confirm-page-container/confirm-page-container-navigation/index.scss new file mode 100755 index 000000000..0cf184c60 --- /dev/null +++ b/ui/app/components/confirm-page-container/confirm-page-container-navigation/index.scss @@ -0,0 +1,54 @@ +.confirm-page-container-navigation { + display: flex; + justify-content: space-between; + font: inherit; + padding: 4px 10px 4px 10px; + border-bottom: 1px solid $geyser; + flex: 0 0 auto; + + &__container { + display: flex; + } + + &__arrow { + cursor: pointer; + display: flex; + padding-left: 5px; + padding-right: 5px; + } + + &__arrow:hover { + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -o-transform: scale(1.1); + transform: scale(1.1); + } + + &__arrow:active { + -webkit-transform: scale(0.95); + -moz-transform: scale(0.95); + -o-transform: scale(0.95); + transform: scale(0.95); + } + + &__textcontainer { + text-align: center; + } + + &__navtext { + font-size: 9px; + font-weight: bold; + } + + &__longtext { + color: $oslo-gray; + font-size: 8px; + } + + &__imageflip { + -webkit-transform: scaleX(-1); + -moz-transform: scaleX(-1); + -o-transform: scaleX(-1); + transform: scaleX(-1); + } +}
\ No newline at end of file diff --git a/ui/app/components/confirm-page-container/confirm-page-container.component.js b/ui/app/components/confirm-page-container/confirm-page-container.component.js index 8b2e47cbb..10edf3b16 100644 --- a/ui/app/components/confirm-page-container/confirm-page-container.component.js +++ b/ui/app/components/confirm-page-container/confirm-page-container.component.js @@ -2,7 +2,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import SenderToRecipient from '../sender-to-recipient' import { PageContainerFooter } from '../page-container' -import { ConfirmPageContainerHeader, ConfirmPageContainerContent } from './' +import { ConfirmPageContainerHeader, ConfirmPageContainerContent, ConfirmPageContainerNavigation } from './' export default class ConfirmPageContainer extends Component { static contextTypes = { @@ -43,6 +43,17 @@ export default class ConfirmPageContainer extends Component { summaryComponent: PropTypes.node, warning: PropTypes.string, unapprovedTxCount: PropTypes.number, + // Navigation + totalTx: PropTypes.number, + positionOfCurrentTx: PropTypes.number, + nextTxId: PropTypes.string, + prevTxId: PropTypes.string, + showNavigation: PropTypes.bool, + onNextTx: PropTypes.func, + firstTx: PropTypes.string, + lastTx: PropTypes.string, + ofText: PropTypes.string, + requestsWaitingText: PropTypes.string, // Footer onCancelAll: PropTypes.func, onCancel: PropTypes.func, @@ -79,11 +90,33 @@ export default class ConfirmPageContainer extends Component { unapprovedTxCount, assetImage, warning, + totalTx, + positionOfCurrentTx, + nextTxId, + prevTxId, + showNavigation, + onNextTx, + firstTx, + lastTx, + ofText, + requestsWaitingText, } = this.props const renderAssetImage = contentComponent || (!contentComponent && !identiconAddress) return ( <div className="page-container"> + <ConfirmPageContainerNavigation + totalTx={totalTx} + positionOfCurrentTx={positionOfCurrentTx} + nextTxId={nextTxId} + prevTxId={prevTxId} + showNavigation={showNavigation} + onNextTx={(txId) => onNextTx(txId)} + firstTx={firstTx} + lastTx={lastTx} + ofText={ofText} + requestsWaitingText={requestsWaitingText} + /> <ConfirmPageContainerHeader showEdit={showEdit} onEdit={() => onEdit()} diff --git a/ui/app/components/confirm-page-container/index.js b/ui/app/components/confirm-page-container/index.js index ee88aa5d3..28b17614e 100644 --- a/ui/app/components/confirm-page-container/index.js +++ b/ui/app/components/confirm-page-container/index.js @@ -1,6 +1,8 @@ export { default } from './confirm-page-container.component' export { default as ConfirmPageContainerHeader } from './confirm-page-container-header' export { default as ConfirmDetailRow } from './confirm-detail-row' +export { default as ConfirmPageContainerNavigation } from './confirm-page-container-navigation' + export { default as ConfirmPageContainerContent, ConfirmPageContainerSummary, diff --git a/ui/app/components/confirm-page-container/index.scss b/ui/app/components/confirm-page-container/index.scss index af7a5b555..d41cd4423 100644 --- a/ui/app/components/confirm-page-container/index.scss +++ b/ui/app/components/confirm-page-container/index.scss @@ -3,3 +3,5 @@ @import './confirm-page-container-header/index'; @import './confirm-detail-row/index'; + +@import './confirm-page-container-navigation/index';
\ No newline at end of file diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js index 7d01aaffb..3a940a505 100644 --- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -2,7 +2,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import ConfirmPageContainer, { ConfirmDetailRow } from '../../confirm-page-container' import { isBalanceSufficient } from '../../send/send.utils' -import { DEFAULT_ROUTE } from '../../../routes' +import { DEFAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE } from '../../../routes' import { INSUFFICIENT_FUNDS_ERROR_KEY, TRANSACTION_ERROR_KEY, @@ -55,6 +55,7 @@ export default class ConfirmTransactionBase extends Component { transactionStatus: PropTypes.string, txData: PropTypes.object, unapprovedTxCount: PropTypes.number, + currentNetworkUnapprovedTxs: PropTypes.object, // Component props action: PropTypes.string, contentComponent: PropTypes.node, @@ -347,6 +348,32 @@ export default class ConfirmTransactionBase extends Component { /> ) } + + handleNextTx (txId) { + const { history, clearConfirmTransaction } = this.props + if (txId) { + clearConfirmTransaction() + history.push(`${CONFIRM_TRANSACTION_ROUTE}/${txId}`) + } + } + + getNavigateTxData () { + const { currentNetworkUnapprovedTxs, txData: { id } = {} } = this.props + const enumUnapprovedTxs = Object.keys(currentNetworkUnapprovedTxs).reverse() + const currentPosition = enumUnapprovedTxs.indexOf(id.toString()) + + return { + totalTx: enumUnapprovedTxs.length, + positionOfCurrentTx: currentPosition + 1, + nextTxId: enumUnapprovedTxs[currentPosition + 1], + prevTxId: enumUnapprovedTxs[currentPosition - 1], + showNavigation: enumUnapprovedTxs.length > 1, + firstTx: enumUnapprovedTxs[0], + lastTx: enumUnapprovedTxs[enumUnapprovedTxs.length - 1], + ofText: this.context.t('ofTextNofM'), + requestsWaitingText: this.context.t('requestsAwaitingAcknowledgement'), + } + } render () { const { @@ -376,6 +403,7 @@ export default class ConfirmTransactionBase extends Component { const { name } = methodData const { valid, errorKey } = this.getErrorKey() + const { totalTx, positionOfCurrentTx, nextTxId, prevTxId, showNavigation, firstTx, lastTx, ofText, requestsWaitingText } = this.getNavigateTxData() return ( <ConfirmPageContainer @@ -401,6 +429,16 @@ export default class ConfirmTransactionBase extends Component { errorMessage={errorMessage || submitError} errorKey={propsErrorKey || errorKey} warning={warning} + totalTx={totalTx} + positionOfCurrentTx={positionOfCurrentTx} + nextTxId={nextTxId} + prevTxId={prevTxId} + showNavigation={showNavigation} + onNextTx={(txId) => this.handleNextTx(txId)} + firstTx={firstTx} + lastTx={lastTx} + ofText={ofText} + requestsWaitingText={requestsWaitingText} disabled={!propsValid || !valid || submitting} onEdit={() => this.handleEdit()} onCancelAll={() => this.handleCancelAll()} diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js index c366d5137..45bf62fb9 100644 --- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js +++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js @@ -73,9 +73,9 @@ const mapStateToProps = (state, props) => { const currentNetworkUnapprovedTxs = R.filter( ({ metamaskNetworkId }) => metamaskNetworkId === network, - valuesFor(unapprovedTxs), + unapprovedTxs, ) - const unapprovedTxCount = currentNetworkUnapprovedTxs.length + const unapprovedTxCount = valuesFor(currentNetworkUnapprovedTxs).length return { balance, @@ -104,6 +104,7 @@ const mapStateToProps = (state, props) => { assetImage, unapprovedTxs, unapprovedTxCount, + currentNetworkUnapprovedTxs, } } diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js index 2c44b6094..76782cf6a 100644 --- a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js +++ b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js @@ -32,21 +32,15 @@ export default class ConfirmTransactionSwitch extends Component { txData, methodData: { name }, fetchingData, - isEtherTransaction, } = this.props const { id, txParams: { data } = {} } = txData - if (isConfirmDeployContract(txData)) { - const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_DEPLOY_CONTRACT_PATH}` - return <Redirect to={{ pathname }} /> - } - if (fetchingData) { return <Loading /> } - if (isEtherTransaction) { - const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_ETHER_PATH}` + if (isConfirmDeployContract(txData)) { + const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_DEPLOY_CONTRACT_PATH}` return <Redirect to={{ pathname }} /> } diff --git a/ui/app/components/transaction-view-balance/transaction-view-balance.component.js b/ui/app/components/transaction-view-balance/transaction-view-balance.component.js index 273845c47..402b0f486 100644 --- a/ui/app/components/transaction-view-balance/transaction-view-balance.component.js +++ b/ui/app/components/transaction-view-balance/transaction-view-balance.component.js @@ -37,13 +37,13 @@ export default class TransactionViewBalance extends PureComponent { className="transaction-view-balance__primary-balance" value={balance} type={PRIMARY} - ethNumberOfDecimals={3} + ethNumberOfDecimals={4} /> <UserPreferencedCurrencyDisplay className="transaction-view-balance__secondary-balance" value={balance} type={SECONDARY} - ethNumberOfDecimals={3} + ethNumberOfDecimals={4} /> </div> ) diff --git a/ui/app/ducks/confirm-transaction.duck.js b/ui/app/ducks/confirm-transaction.duck.js index 275eb1551..e228d2d39 100644 --- a/ui/app/ducks/confirm-transaction.duck.js +++ b/ui/app/ducks/confirm-transaction.duck.js @@ -370,11 +370,16 @@ export function setTransactionToConfirm (transactionId) { dispatch(setFetchingData(true)) const methodData = await getMethodData(data) dispatch(updateMethodData(methodData)) + } catch (error) { + dispatch(updateMethodData({})) + dispatch(setFetchingData(false)) + } + + try { const toSmartContract = await isSmartContractAddress(to) dispatch(updateToSmartContract(toSmartContract)) dispatch(setFetchingData(false)) } catch (error) { - dispatch(updateMethodData({})) dispatch(setFetchingData(false)) } diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index 22fa53098..dfa2f3656 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -74,6 +74,7 @@ function reduceMetamask (state, action) { case actions.CLEAR_NOTICES: return extend(metamaskState, { noActiveNotices: true, + nextUnreadNotice: undefined, }) case actions.UPDATE_METAMASK_STATE: @@ -294,8 +295,10 @@ function reduceMetamask (state, action) { amount: '0x0', memo: '', errors: {}, + maxModeOn: false, editingTransactionId: null, forceGasMin: null, + toNickname: '', }, }) @@ -333,9 +336,9 @@ function reduceMetamask (state, action) { }) case actions.SET_USE_BLOCKIE: - return extend(metamaskState, { - useBlockie: action.value, - }) + return extend(metamaskState, { + useBlockie: action.value, + }) case actions.UPDATE_FEATURE_FLAGS: return extend(metamaskState, { |