aboutsummaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/_locales/cs/messages.json912
-rw-r--r--app/_locales/en/messages.json3
-rw-r--r--app/_locales/hn/messages.json20
-rw-r--r--app/_locales/index.json3
-rw-r--r--app/_locales/ja/messages.json99
-rw-r--r--app/_locales/tml/messages.json912
-rw-r--r--app/_locales/tr/messages.json912
-rw-r--r--app/manifest.json2
-rw-r--r--app/scripts/background.js229
-rw-r--r--app/scripts/config.js44
-rw-r--r--app/scripts/contentscript.js42
-rw-r--r--app/scripts/controllers/address-book.js66
-rw-r--r--app/scripts/controllers/blacklist.js1
-rw-r--r--app/scripts/controllers/computed-balances.js45
-rw-r--r--app/scripts/controllers/currency.js68
-rw-r--r--app/scripts/controllers/infura.js1
-rw-r--r--app/scripts/controllers/network/enums.js56
-rw-r--r--app/scripts/controllers/network/index.js2
-rw-r--r--app/scripts/controllers/network/network.js (renamed from app/scripts/controllers/network.js)28
-rw-r--r--app/scripts/controllers/network/util.js65
-rw-r--r--app/scripts/controllers/preferences.js124
-rw-r--r--app/scripts/controllers/recent-blocks.js1
-rw-r--r--app/scripts/controllers/shapeshift.js75
-rw-r--r--app/scripts/controllers/token-rates.js77
-rw-r--r--app/scripts/controllers/transactions.js1
-rw-r--r--app/scripts/edge-encryptor.js142
-rw-r--r--app/scripts/first-time-state.js19
-rw-r--r--app/scripts/inpage.js20
-rw-r--r--app/scripts/lib/ComposableObservableStore.js49
-rw-r--r--app/scripts/lib/buy-eth-url.js11
-rw-r--r--app/scripts/lib/config-manager.js45
-rw-r--r--app/scripts/lib/createLoggerMiddleware.js16
-rw-r--r--app/scripts/lib/createOriginMiddleware.js12
-rw-r--r--app/scripts/lib/createProviderMiddleware.js6
-rw-r--r--app/scripts/lib/enums.js9
-rw-r--r--app/scripts/lib/environment-type.js10
-rw-r--r--app/scripts/lib/events-proxy.js25
-rw-r--r--app/scripts/lib/get-first-preferred-lang-code.js7
-rw-r--r--app/scripts/lib/hex-to-bn.js7
-rw-r--r--app/scripts/lib/is-popup-or-notification.js11
-rw-r--r--app/scripts/lib/local-store.js38
-rw-r--r--app/scripts/lib/migrator/index.js35
-rw-r--r--app/scripts/lib/nodeify.js8
-rw-r--r--app/scripts/lib/personal-message-manager.js1
-rw-r--r--app/scripts/lib/port-stream.js37
-rw-r--r--app/scripts/lib/seed-phrase-verifier.js1
-rw-r--r--app/scripts/lib/setupMetamaskMeshMetrics.js3
-rw-r--r--app/scripts/lib/stream-utils.js18
-rw-r--r--app/scripts/lib/tx-state-manager.js6
-rw-r--r--app/scripts/lib/typed-message-manager.js2
-rw-r--r--app/scripts/lib/util.js81
-rw-r--r--app/scripts/metamask-controller.js586
-rw-r--r--app/scripts/migrations/013.js7
-rw-r--r--app/scripts/migrations/015.js15
-rw-r--r--app/scripts/migrations/016.js22
-rw-r--r--app/scripts/migrations/017.js21
-rw-r--r--app/scripts/migrations/018.js39
-rw-r--r--app/scripts/migrations/019.js44
-rw-r--r--app/scripts/migrations/022.js17
-rw-r--r--app/scripts/migrations/023.js38
-rw-r--r--app/scripts/platforms/sw.js27
-rw-r--r--app/scripts/platforms/window.js23
-rw-r--r--app/scripts/popup-core.js25
-rw-r--r--app/scripts/ui.js8
64 files changed, 4678 insertions, 601 deletions
diff --git a/app/_locales/cs/messages.json b/app/_locales/cs/messages.json
new file mode 100644
index 000000000..6a4ebc8a5
--- /dev/null
+++ b/app/_locales/cs/messages.json
@@ -0,0 +1,912 @@
+{
+ "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/en/messages.json b/app/_locales/en/messages.json
index b372326ee..3b20ab49a 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -908,5 +908,8 @@
},
"youSign": {
"message": "You are signing"
+ },
+ "generatingTransaction": {
+ "message": "Generating transaction"
}
}
diff --git a/app/_locales/hn/messages.json b/app/_locales/hn/messages.json
index 323f4b4b3..b869560e5 100644
--- a/app/_locales/hn/messages.json
+++ b/app/_locales/hn/messages.json
@@ -9,7 +9,7 @@
"message": "खाता विवरण"
},
"accountName": {
- "message": "खाता का नाम"
+ "message": "खाते का नाम"
},
"address": {
"message": "खाते का पता"
@@ -21,7 +21,7 @@
"message": "टोकन जोड़ें"
},
"addTokens": {
- "message": "टोकनो को जोड़ें"
+ "message": "टोकनों को जोड़ें"
},
"amount": {
"message": "राशि"
@@ -30,7 +30,7 @@
"message": "राशि + गैस"
},
"appDescription": {
- "message": "एथरेम ब्राउज़र एक्सटेंशन",
+ "message": "इथीरियम ब्राउज़र एक्सटेंशन",
"description": "आवेदन का विवरण"
},
"appName": {
@@ -53,7 +53,7 @@
"message": "उपलब्ध बैलेंस।"
},
"balances": {
- "message": "ापके उपलब्ध बैलेंस"
+ "message": "आपके उपलब्ध बैलेंस"
},
"balanceIsInsufficientGas": {
"message": "वर्तमान गैस कुल के लिए अपर्याप्त शेष"
@@ -78,10 +78,10 @@
"message": "खरीदें"
},
"buyCoinbase": {
- "message": "कॉनबेस पर खरीदें"
+ "message": "कॉइनबेस पर खरीदें"
},
"buyCoinbaseExplainer": {
- "message": "बिल्टकोइन, एथरेम और लाइटकोइन खरीदने और बेचने के लिए दुनिया का सबसे लोकप्रिय तरीका Coinbase है।"
+ "message": "बिल्टकोइन, इथीरियम और लाइटकोइन खरीदने और बेचने के लिए दुनिया का सबसे लोकप्रिय तरीका कॉइनबेस है।"
},
"cancel": {
"message": "रद्द करें"
@@ -108,7 +108,7 @@
"message": "जारी रखें"
},
"continueToCoinbase": {
- "message": "कॉ्ोनबेस को ब्हेजना जारी रखें"
+ "message": "कॉइनबेस को ब्हेजना जारी रखें"
},
"contractDeployment": {
"message": "अनुबंध परिनियोजन व तैनाती"
@@ -435,13 +435,13 @@
"message": "बीज शब्द में केवल लोअरकेस वर्ण होते हैं"
},
"mainnet": {
- "message": "मुख्य ईथरम नेटवर्क"
+ "message": "मुख्य इथीरियम नेटवर्क"
},
"message": {
"message": "संदेश"
},
"metamaskDescription": {
- "message": "मेटामास्क एथर्मम के लिए एक सुरक्षित पहचान वॉल्ट है।"
+ "message": "मेटामास्क इथीरियम के लिए एक सुरक्षित पहचान वॉल्ट है।"
},
"min": {
"message": "न्यूनतम"
@@ -649,7 +649,7 @@
"message": "भेजें टोकन"
},
"sendTokensAnywhere": {
- "message": "इटोरम खाते वाले किसी को भी टोकन भेजें"
+ "message": "इथीरियम खाते वाले किसी को भी टोकन भेजें"
},
"settings": {
"message": "सेटिंग्स"
diff --git a/app/_locales/index.json b/app/_locales/index.json
index c085deb72..7717502b7 100644
--- a/app/_locales/index.json
+++ b/app/_locales/index.json
@@ -1,4 +1,5 @@
[
+ { "code": "cs", "name": "Czech" },
{ "code": "de", "name": "German" },
{ "code": "en", "name": "English" },
{ "code": "es", "name": "Spanish" },
@@ -13,6 +14,8 @@
{ "code": "ru", "name": "Russian" },
{ "code": "sl", "name": "Slovenian" },
{ "code": "th", "name": "Thai" },
+ { "code": "tml", "name": "Tamil" },
+ { "code": "tr", "name": "Turkish" },
{ "code": "vi", "name": "Vietnamese" },
{ "code": "zh_CN", "name": "Mandarin" },
{ "code": "zh_TW", "name": "Taiwanese" }
diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json
index d9762a3e9..3a664ec00 100644
--- a/app/_locales/ja/messages.json
+++ b/app/_locales/ja/messages.json
@@ -20,6 +20,9 @@
"addToken": {
"message": "トークンを追加"
},
+ "addTokens": {
+ "message": "トークンを追加"
+ },
"amount": {
"message": "金額"
},
@@ -46,6 +49,9 @@
"balance": {
"message": "残高:"
},
+ "balances": {
+ "message": "トークン残高"
+ },
"balanceIsInsufficientGas": {
"message": "現在のガス総量に対して残高が不足しています"
},
@@ -63,10 +69,10 @@
"message": "購入"
},
"buyCoinbase": {
- "message": "Coinbaseで購入"
+ "message": "Coinbaseのサイトで購入"
},
"buyCoinbaseExplainer": {
- "message": "Coinbaseは、世界的なBitcoin、Ethereum、そしてLitecoinの取引所です。"
+ "message": "Etherを購入できます。Coinbaseは、世界的なBitcoin、Ethereum、そしてLitecoinの取引所です。"
},
"cancel": {
"message": "キャンセル"
@@ -90,7 +96,7 @@
"message": "トランザクションの確認"
},
"continueToCoinbase": {
- "message": "Coinbaseで続行"
+ "message": "Coinbaseを開く"
},
"contractDeployment": {
"message": "コントラクトのデプロイ"
@@ -138,6 +144,9 @@
"customGas": {
"message": "ガスのカスタマイズ"
},
+ "customToken": {
+ "message": "カスタムトークン"
+ },
"customize": {
"message": "カスタマイズ"
},
@@ -154,20 +163,20 @@
"message": "DENとは、あなたのパスワードが暗号化されたMetaMask内のストレージです。"
},
"deposit": {
- "message": "受取り"
+ "message": "振込"
},
"depositBTC": {
- "message": "あなたのBTCを次のアドレスへデポジット:"
+ "message": "BTCを下記のアドレスへ振込んでください:"
},
"depositCoin": {
- "message": "あなたの $1を次のアドレスへデポジット",
+ "message": "$1を下記のアドレスへ振込んでください",
"description": "Tells the user what coin they have selected to deposit with shapeshift"
},
"depositEth": {
- "message": "ETHをデポジット"
+ "message": "ETHを入金"
},
"depositEther": {
- "message": "Etherをデポジット"
+ "message": "Etherを振込"
},
"depositFiat": {
"message": "法定通貨でデポジット"
@@ -176,10 +185,10 @@
"message": "別のアカウントから入金"
},
"depositShapeShift": {
- "message": "ShapeShiftで入金"
+ "message": "ShapeShiftで交換"
},
"depositShapeShiftExplainer": {
- "message": "他の暗号通貨をEtherと交換してMetaMaskのウォレットへ入金できます。アカウント作成は不要です。"
+ "message": "他の暗号通貨とEtherを交換して、MetaMaskのウォレットへ入金できます。アカウント作成は不要です。"
},
"details": {
"message": "詳細"
@@ -188,10 +197,10 @@
"message": "ダイレクトデポジット"
},
"directDepositEther": {
- "message": "Etherを直接受け取り"
+ "message": "Etherを直接入金"
},
"directDepositEtherExplainer": {
- "message": "Etherをすでにお持ちなら、MetaMaskの新しいウォレットにEtherを送信することができます。"
+ "message": "既にEtherをお持ちなら、MetaMaskの新しいウォレットにEtherを送信することができます。"
},
"done": {
"message": "完了"
@@ -209,7 +218,7 @@
"message": "パスワードを入力"
},
"etherscanView": {
- "message": "Etherscanでアカウントを参照"
+ "message": "Etherscanでアカウントを確認"
},
"exchangeRate": {
"message": "交換レート"
@@ -266,7 +275,7 @@
"message": "必要ガスプライス"
},
"getEther": {
- "message": "Etherをゲット"
+ "message": "Etherを取得する"
},
"getEtherFromFaucet": {
"message": "フォーセットで $1のEtherを得ることができます。",
@@ -287,7 +296,7 @@
"message": "トークンを隠す"
},
"hideTokenPrompt": {
- "message": "トークンを隠しますか??"
+ "message": "トークンを隠しますか?"
},
"howToDeposit": {
"message": "どのようにEtherをデポジットしますか?"
@@ -300,7 +309,7 @@
"message": "アカウントのインポート"
},
"importAccountMsg": {
- "message":"追加したアカウントはMetaMaskのアカウントシードフレーズとは関連付けられません。インポートしたアカウントについての詳細は"
+ "message":"追加したアカウントはMetaMaskのアカウントパスフレーズとは関連付けられません。インポートしたアカウントについての詳細は"
},
"importAnAccount": {
"message": "アカウントをインポート"
@@ -334,13 +343,22 @@
"message": "JSONファイル",
"description": "format for importing an account"
},
+ "keepTrackTokens": {
+ "message": "MetaMaskアカウントで入手したトークンを検索できます。"
+ },
"kovan": {
"message": "Kovanテストネットワーク"
},
+ "learnMore": {
+ "message": "詳細"
+ },
"lessThanMax": {
"message": " $1以下にして下さい。",
"description": "helper for inputting hex as decimal input"
},
+ "likeToAddTokens": {
+ "message": "トークンを追加しますか?"
+ },
"limit": {
"message": "リミット"
},
@@ -371,8 +389,11 @@
"myAccounts": {
"message": "マイアカウント"
},
+ "mustSelectOne": {
+ "message": "一つ以上のトークンを選択してください。"
+ },
"needEtherInWallet": {
- "message": "MetaMaskを使って分散型アプリケーションを使用するためには、このウォレットにEtherが必要です。"
+ "message": "MetaMaskで分散型アプリケーションを使用するためには、このウォレットにEtherが必要です。"
},
"needImportFile": {
"message": "インポートするファイルを選択してください。",
@@ -411,7 +432,7 @@
"message": "この名前にはアドレスが設定されていません。"
},
"noDeposits": {
- "message": "デポジットがありません。"
+ "message": "振込みがありません。"
},
"noTransactionHistory": {
"message": "トランザクション履歴がありません。"
@@ -445,10 +466,13 @@
"description": "For importing an account from a private key"
},
"pasteSeed": {
- "message": "シードをここにペーストして下さい!"
+ "message": "パスフレーズをここにペーストして下さい!"
},
"pleaseReviewTransaction": {
- "message": "トランザクションをレビューして下さい。"
+ "message": "トランザクションを確認して下さい。"
+ },
+ "popularTokens": {
+ "message": "人気のトークン"
},
"privateKey": {
"message": "秘密鍵",
@@ -470,13 +494,13 @@
"message": "もっと読む"
},
"receive": {
- "message": "受け取る"
+ "message": "受取"
},
"recipientAddress": {
"message": "受取人アドレス"
},
"refundAddress": {
- "message": "あなたの返金先アドレス"
+ "message": "受取アドレス"
},
"rejected": {
"message": "拒否されました"
@@ -487,12 +511,18 @@
"restoreFromSeed": {
"message": "パスフレーズから復元する"
},
+ "restoreVault": {
+ "message": "ウォレットを復元する"
+ },
"required": {
"message": "必要です。"
},
"retryWithMoreGas": {
"message": "より高いガスプライスで再度試して下さい。"
},
+ "walletSeed": {
+ "message": "ウォレットのパスフレーズ"
+ },
"revealSeedWords": {
"message": "パスフレーズを表示"
},
@@ -519,23 +549,35 @@
"selectService": {
"message": "サービスを選択"
},
+ "selectType": {
+ "message": "キーの種類"
+ },
"send": {
"message": "送信"
},
+ "sendETH": {
+ "message": "ETHの送信"
+ },
"sendTokens": {
"message": "トークンを送る"
},
"onlySendToEtherAddress": {
"message": "ETHはイーサリウムアカウントのみに送信できます。"
},
+ "searchTokens": {
+ "message": "トークンの検索"
+ },
"sendTokensAnywhere": {
"message": "イーサリアムアカウントを持っている人にトークンを送る"
},
"settings": {
"message": "設定"
},
+ "info": {
+ "message": "情報"
+ },
"shapeshiftBuy": {
- "message": "Shapeshiftで買う"
+ "message": "Shapeshiftで交換"
},
"showPrivateKeys": {
"message": "秘密鍵を表示"
@@ -565,13 +607,13 @@
"message": "送信"
},
"takesTooLong": {
- "message": "長くかかりすぎていますか?"
+ "message": "送信に時間がかかりますか?"
},
"testFaucet": {
"message": "Faucetをテスト"
},
"to": {
- "message": "宛先"
+ "message": "送信先"
},
"toETHviaShapeShift": {
"message": "ShapeShiftで $1をETHにする",
@@ -595,6 +637,9 @@
"total": {
"message": "合計"
},
+ "transactions": {
+ "message": "トランザクション"
+ },
"transactionMemo": {
"message": "トランザクションメモ (オプション)"
},
@@ -609,7 +654,7 @@
"description": "Followed by a link (here) to view token balances"
},
"typePassword": {
- "message": "パスワードタイプ"
+ "message": "パスワードの入力"
},
"uiWelcome": {
"message": "新UIへようこそ!(ベータ版)"
@@ -646,7 +691,7 @@
"message": "警告"
},
"whatsThis": {
- "message": "これは何でしょう?"
+ "message": "この機能について"
},
"yourSigRequested": {
"message": "あなたの署名がリクエストされています。"
diff --git a/app/_locales/tml/messages.json b/app/_locales/tml/messages.json
new file mode 100644
index 000000000..fcc418bac
--- /dev/null
+++ b/app/_locales/tml/messages.json
@@ -0,0 +1,912 @@
+{
+ "accept": {
+ "message": "ஏற்கவும்"
+ },
+ "account": {
+ "message": "கணக்கு"
+ },
+ "accountDetails": {
+ "message": "கணக்கு விவரங்கள்"
+ },
+ "accountName": {
+ "message": "கணக்கின் பெயர்"
+ },
+ "address": {
+ "message": "முகவரி"
+ },
+ "addCustomToken": {
+ "message": "தனிப்பயன் டோக்கனைச் சேர்க்கவும்"
+ },
+ "addToken": {
+ "message": "டோக்கனைச் சேர்"
+ },
+ "addTokens": {
+ "message": "டோக்கன்களைச் சேர்"
+ },
+ "amount": {
+ "message": "தொகை"
+ },
+ "amountPlusGas": {
+ "message": "தொகை + எரிவாயு"
+ },
+ "appDescription": {
+ "message": "எதெரியும் பிரௌசர் நீட்டிப்பு",
+ "description": "பயன்பாட்டின் விளக்கம்"
+ },
+ "appName": {
+ "message": "மேடமஸ்க் ",
+ "description": "பயன்பாட்டின் பெயர்"
+ },
+ "approved": {
+ "message": "அங்கீகரிக்கப்பட்ட"
+ },
+ "attemptingConnect": {
+ "message": "இணைக்க முயற்சி செய்க ப்ளாக்சைன்"
+ },
+ "attributions": {
+ "message": "பண்புகளும்"
+ },
+ "available": {
+ "message": "கிடைக்கும்"
+ },
+ "back": {
+ "message": "மீண்டும்"
+ },
+ "balance": {
+ "message": "இருப்பு:"
+ },
+ "balances": {
+ "message": "உங்கள் இருப்பு"
+ },
+ "balanceIsInsufficientGas": {
+ "message": "நடப்பு வாயு மொத்தம் போதுமான சமநிலை"
+ },
+ "beta": {
+ "message": "பீட்டா"
+ },
+ "betweenMinAndMax": {
+ "message": "$ 1 க்கும் அதிகமாகவும் அல்லது $ 2 க்கு சமமாகவும் இருக்க வேண்டும்.",
+ "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி"
+ },
+ "blockiesIdenticon": {
+ "message": "ப்ளாக்கிஸ் ஐடென்டிகோன் பயன்பாட்டு"
+ },
+ "borrowDharma": {
+ "message": "தர்மத்துடன் கடன் வாங்குங்கள் (பீட்டா)"
+ },
+ "builtInCalifornia": {
+ "message": "மேடமஸ்க் வடிவமைக்கப்பட்டு கலிபோர்னியாவில் கட்டப்பட்டுள்ளது."
+ },
+ "buy": {
+ "message": "வாங்க"
+ },
+ "buyCoinbase": {
+ "message": "கோஇன்பசே வாங்கவும்"
+ },
+ "buyCoinbaseExplainer": {
+ "message": "கோஇன்பசே பிறகாய்ன் , எதெரியும் மற்றும் ளிட்டசோன் வாங்க மற்றும் விற்க உலகின் மிகவும் பிரபலமான வழி"
+ },
+ "ok": {
+ "message": "சரி"
+ },
+ "cancel": {
+ "message": "ரத்து"
+ },
+ "classicInterface": {
+ "message": "கிளாசிக் இடைமுகத்தைப் பயன்படுத்தவும்"
+ },
+ "clickCopy": {
+ "message": "நகலெடுக்க கிளிக் செய்யவும்"
+ },
+ "confirm": {
+ "message": "உறுதிப்படுத்தவும்"
+ },
+ "confirmed": {
+ "message": "உறுதி"
+ },
+ "confirmContract": {
+ "message": "ஒப்பந்தத்தை உறுதிப்படுத்துக"
+ },
+ "confirmPassword": {
+ "message": "கடவுச்சொல்லை உறுதிப்படுத்துக"
+ },
+ "confirmTransaction": {
+ "message": "பரிவர்த்தனை உறுதிபடுத்தவும்"
+ },
+ "continue": {
+ "message": "தொடர்ந்து"
+ },
+ "continueToCoinbase": {
+ "message": "கோஇன்பசே ஐத் தொடரவும்"
+ },
+ "contractDeployment": {
+ "message": "ஒப்பந்த வரிசைப்படுத்தல்"
+ },
+ "conversionProgress": {
+ "message": "மாற்றம் முன்னேற்றம்"
+ },
+ "copiedButton": {
+ "message": "நகலெடுக்கப்பட்டன"
+ },
+ "copiedClipboard": {
+ "message": "கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது"
+ },
+ "copiedExclamation": {
+ "message": "நகலெடுக்கப்பட்டன!"
+ },
+ "copiedSafe": {
+ "message": "நான் எங்காவது பாதுகாப்பாக நகலெடுத்திருக்கிறேன்"
+ },
+ "copy": {
+ "message": "நகல்"
+ },
+ "copyToClipboard": {
+ "message": "கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது"
+ },
+ "copyButton": {
+ "message": " நகல் "
+ },
+ "copyPrivateKey": {
+ "message": "இது உங்கள் தனிப்பட்ட விசை (நகலெடுக்க கிளிக் செய்யவும்)"
+ },
+ "create": {
+ "message": "உருவாக்கவும்"
+ },
+ "createAccount": {
+ "message": "உங்கள் கணக்கை துவங்குங்கள்"
+ },
+ "createDen": {
+ "message": "உருவாக்கவும்"
+ },
+ "crypto": {
+ "message": "கிரிப்டோ",
+ "description": "பரிமாற்ற வகை (கிரிப்டோசுர்ரென்சிஸ்)"
+ },
+ "currentConversion": {
+ "message": "தற்போதைய மாற்றம்"
+ },
+ "currentNetwork": {
+ "message": "தற்போதைய நெட்வொர்க்"
+ },
+ "customGas": {
+ "message": "எரிவாயுவைத் தனிப்பயனாக்குங்கள்"
+ },
+ "customToken": {
+ "message": "தனிப்பயன் டோக்கன்"
+ },
+ "customize": {
+ "message": "தனிப்பயனாக்கலாம்"
+ },
+ "customRPC": {
+ "message": "விருப்ப RPC ஐ"
+ },
+ "decimalsMustZerotoTen": {
+ "message": "தசமங்கள் குறைந்தபட்சம் 0, மற்றும் 36 க்கு மேல் இருக்க வேண்டும்."
+ },
+ "decimal": {
+ "message": "துல்லியத்தின் முடிவு"
+ },
+ "defaultNetwork": {
+ "message": "எதிர் பரிவர்த்தனைகளுக்கான முன்னிருப்பு வலையமைப்பு முதன்மை நிகரமாகும்."
+ },
+ "denExplainer": {
+ "message": "உங்கள் DEN என்பது உங்கள் கடவுச்சொல்-மறைகுறியாக்கப்பட்ட சேமிப்பகம் மெட்டாமாஸ்க்கிற்குள்."
+ },
+ "deposit": {
+ "message": "வைப்புத்தொகை"
+ },
+ "depositBTC": {
+ "message": "கீழே உங்கள் முகவரிக்கு உங்கள் BTC வைப்போம்:"
+ },
+ "depositCoin": {
+ "message": "உங்கள் முகவரிக்கு $ 1 ஐ கீழே உள்ளிடவும்",
+ "description": "சேபஷிபிட் உடன் வைப்புக்குத் தேர்ந்தெடுக்கப்பட்ட நாணயத்தை பயனரிடம் கூறுகிறார்"
+ },
+ "depositEth": {
+ "message": "வைப்புத்தொகை எது "
+ },
+ "depositEther": {
+ "message": "வைப்புத்தொகை எதிர் "
+ },
+ "depositFiat": {
+ "message": "ஃபியட் உடன் வைப்பு"
+ },
+ "depositFromAccount": {
+ "message": "மற்றொரு கணக்கிலிருந்து வைப்பு"
+ },
+ "depositShapeShift": {
+ "message": "ShapeShift உடன் வைப்பு"
+ },
+ "depositShapeShiftExplainer": {
+ "message": "நீங்கள் மற்ற கிரிப்டோகிராரன்கள் சொந்தமாக வைத்திருந்தால், உங்கள் மெட்டாமாஸ்க் பணப்பையில் நேரடியாக ஈதரை வர்த்தகம் செய்யலாம் மற்றும் வைப்பு செய்யலாம். கணக்கு தேவையில்லை."
+ },
+ "details": {
+ "message": "விவரங்கள்"
+ },
+ "directDeposit": {
+ "message": "நேரடி வைப்பு"
+ },
+ "directDepositEther": {
+ "message": "நேரடியாக வைப்புத்தொகை"
+ },
+ "directDepositEtherExplainer": {
+ "message": "நீங்கள் ஏற்கனவே ஏதெர் இருந்தால், நேரடி வைப்பு மூலம் உங்கள் புதிய பணப்பையில் ஈத்தர் பெற விரைவான வழி."
+ },
+ "done": {
+ "message": "முடிந்தது"
+ },
+ "downloadStateLogs": {
+ "message": "மாநில பதிவுகள் பதிவிறக்க"
+ },
+ "dropped": {
+ "message": "நீக்கப்பட்டார்"
+ },
+ "edit": {
+ "message": "தொகு"
+ },
+ "editAccountName": {
+ "message": "கணக்கு பெயரை மாற்றுக"
+ },
+ "emailUs": {
+ "message": "எங்களுக்கு மின்னஞ்சல்!"
+ },
+ "encryptNewDen": {
+ "message": "உங்கள் புதிய DEN ஐ குறியாக்குக"
+ },
+ "enterPassword": {
+ "message": "கடவுச்சொல்லை உள்ளிடவும்"
+ },
+ "enterPasswordConfirm": {
+ "message": "உறுதிப்படுத்த உங்கள் கடவுச்சொல்லை உள்ளிடவும்"
+ },
+ "passwordNotLongEnough": {
+ "message": "கடவுச்சொல் போதாது"
+ },
+ "passwordsDontMatch": {
+ "message": "கடவுச்சொற்கள் பொருந்தாதே"
+ },
+ "etherscanView": {
+ "message": "Etherscan கணக்கைப் பார்க்கவும்"
+ },
+ "exchangeRate": {
+ "message": "மாற்று விகிதம்"
+ },
+ "exportPrivateKey": {
+ "message": "தனியார் விசை ஐ ஏற்றுமதி செய்க"
+ },
+ "exportPrivateKeyWarning": {
+ "message": "தனிப்பட்ட விசைகளை உங்கள் சொந்த ஆபத்தில் ஏற்றுமதி செய்யுங்கள்."
+ },
+ "failed": {
+ "message": "தோல்வி"
+ },
+ "fiat": {
+ "message": "FIAT",
+ "description": "பரிமாற்ற வகை"
+ },
+ "fileImportFail": {
+ "message": "கோப்பு இறக்குமதி வேலை செய்யவில்லையா? இங்கே கிளிக் செய்யவும்!",
+ "description": "JSON கோப்பில் பயனர் கணக்கை தங்கள் கணக்கை இறக்குமதி செய்ய உதவுகிறது"
+ },
+ "followTwitter": {
+ "message": "Twitter இல் எங்களைப் பின்தொடரவும்"
+ },
+ "from": {
+ "message": "இருந்து"
+ },
+ "fromToSame": {
+ "message": "இருந்து மற்றும் முகவரி அதே இருக்க முடியாது"
+ },
+ "fromShapeShift": {
+ "message": "ShapeShift இலிருந்து"
+ },
+ "gas": {
+ "message": "எரிவாயு",
+ "description": "எரிவாயு விலை குறையும்"
+ },
+ "gasFee": {
+ "message": "எரிவாயு கட்டணம்"
+ },
+ "gasLimit": {
+ "message": "எரிவாயு வரம்பு"
+ },
+ "gasLimitCalculation": {
+ "message": "நெட்வொர்க் வெற்றி விகிதங்களின் அடிப்படையில் பரிந்துரைக்கப்பட்ட எரிவாயு வரம்பை நாங்கள் கணக்கிடுகிறோம்."
+ },
+ "gasLimitRequired": {
+ "message": "எரிவாயு வரம்பு தேவை"
+ },
+ "gasLimitTooLow": {
+ "message": "எரிவாயு வரம்பு குறைந்தது 21000 ஆக இருக்க வேண்டும்"
+ },
+ "generatingSeed": {
+ "message": "விதை உருவாக்குகிறது ..."
+ },
+ "gasPrice": {
+ "message": "எரிவாயு விலை (GWEI)"
+ },
+ "gasPriceCalculation": {
+ "message": "நெட்வொர்க் வெற்றி விகிதங்களின் அடிப்படையில் பரிந்துரைக்கப்பட்ட எரிவாயு விலைகளை நாங்கள் கணக்கிடுகிறோம்."
+ },
+ "gasPriceRequired": {
+ "message": "எரிவாயு விலை தேவைப்படுகிறது"
+ },
+ "getEther": {
+ "message": "ஈத்தர் கிடைக்கும்"
+ },
+ "getEtherFromFaucet": {
+ "message": "$ 1 க்கு ஒரு குழாய் இருந்து ஈதர் கிடைக்கும்$1",
+ "description": "ஈத்தர் குழாய் ஐந்து பிணைய பெயர் காட்டுகிறது"
+ },
+ "greaterThanMin": {
+ "message": "$ 1 க்கும் அதிகமாகவோ அல்லது சமமாகவோ இருக்க வேண்டும்",
+ "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி"
+ },
+ "here": {
+ "message": "இங்கே",
+ "description": "இங்கே-கிளிக் செய்யவும்- மேலும் தகவலுக்கு (troubleTokenBalances செல்கிறது)"
+ },
+ "hereList": {
+ "message": "இங்கே ஒரு பட்டியல் !!!!"
+ },
+ "hide": {
+ "message": "மறை"
+ },
+ "hideToken": {
+ "message": "டோக்கனை மறை"
+ },
+ "hideTokenPrompt": {
+ "message": "டோக்கனை மறை?"
+ },
+ "howToDeposit": {
+ "message": "எப்படி ஈத்தர் வைப்பது?"
+ },
+ "holdEther": {
+ "message": "இது நீங்கள் ஈத்தர் மற்றும் டோக்கன்களை வைத்திருக்க உதவுகிறது, மற்றும் பரவலாக்கப்பட்ட பயன்பாடுகளுக்கு உங்கள் பாலமாக செயல்படுகிறது."
+ },
+ "import": {
+ "message": "இறக்குமதி",
+ "description": "தேர்ந்தெடுக்கப்பட்ட கோப்பிலிருந்து ஒரு கணக்கை இறக்குமதி செய்ய பொத்தானை அழுத்தவும்"
+ },
+ "importAccount": {
+ "message": "கணக்கை இறக்குமதி செய்க"
+ },
+ "importAccountMsg": {
+ "message":" இறக்குமதி செய்யப்பட்ட கணக்கு உங்கள் முதலில் உருவாக்கப்பட்ட மெட்டாமாஸ்க் கணக்கு விதை மூலம் தொடர்புடையதாக இருக்காது. இறக்குமதி செய்யப்பட்ட கணக்குகள் பற்றி மேலும் அறிக "
+ },
+ "importAnAccount": {
+ "message": "ஒரு கணக்கை இறக்குமதி செய்க"
+ },
+ "importDen": {
+ "message": "இறக்குமதி DEN இறக்குமதி"
+ },
+ "imported": {
+ "message": "இறக்குமதி",
+ "description": "ஒரு கணக்கு முழுமையாக விசைப்பலகையில் ஏற்றப்பட்டதைக் காட்டுகிறது"
+ },
+ "infoHelp": {
+ "message": "தகவல் மற்றும் உதவி"
+ },
+ "insufficientFunds": {
+ "message": "போதுமான பணம் இல்லை."
+ },
+ "insufficientTokens": {
+ "message": "போதுமான டோக்கன்கள்."
+ },
+ "invalidAddress": {
+ "message": "தவறான முகவரி"
+ },
+ "invalidAddressRecipient": {
+ "message": "பெறுநர் முகவரி தவறானது"
+ },
+ "invalidGasParams": {
+ "message": "தவறான எரிவாயு அளவுருக்கள்"
+ },
+ "invalidInput": {
+ "message": "தவறான உள்ளீடு.."
+ },
+ "invalidRequest": {
+ "message": "தவறான கோரிக்கை"
+ },
+ "invalidRPC": {
+ "message": "தவறான RPC URI"
+ },
+ "jsonFail": {
+ "message": "ஏதோ தவறு நடந்துவிட்டது. உங்கள் JSON கோப்பு ஒழுங்காக வடிவமைக்கப்பட்டுள்ளது என்பதை உறுதிப்படுத்தவும்"
+ },
+ "jsonFile": {
+ "message": "JSON கோப்பு",
+ "description": "ஒரு கணக்கை இறக்குமதி செய்ய வடிவமைக்கப்பட்டுள்ளது"
+ },
+ "keepTrackTokens": {
+ "message": "உங்கள் மேடமஸ்க் கணக்குடன் நீங்கள் வாங்கிய டோக்கன்களை கண்காணியுங்கள்."
+ },
+ "kovan": {
+ "message": "கோவன் டெஸ்ட் நெட்வொர்க்"
+ },
+ "knowledgeDataBase": {
+ "message": "எங்கள் அறிவுத் தளத்தைப் பார்வையிடவும்"
+ },
+ "max": {
+ "message": "மேக்ஸ்"
+ },
+ "learnMore": {
+ "message": "மேலும் அறிக"
+ },
+ "lessThanMax": {
+ "message": "$ 1 க்கும் குறைவாகவோ அல்லது சமமாகவோ இருக்க வேண்டும்.",
+ "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி"
+ },
+ "likeToAddTokens": {
+ "message": "இந்த டோக்கன்களைச் சேர்க்க விரும்புகிறீர்களா?"
+ },
+ "links": {
+ "message": "இணைப்புகள்"
+ },
+ "limit": {
+ "message": "அளவு"
+ },
+ "loading": {
+ "message": "ஏற்றுதல் ..."
+ },
+ "loadingTokens": {
+ "message": "டோக்கன்களை ஏற்றுகிறது ..."
+ },
+ "localhost": {
+ "message": "லோக்கல் ஹோஸ்ட் 8545"
+ },
+ "login": {
+ "message": "உள் நுழை"
+ },
+ "logout": {
+ "message": "வெளியேறு"
+ },
+ "loose": {
+ "message": "லூஸ்"
+ },
+ "loweCaseWords": {
+ "message": "விதை வார்த்தைகள் ஸ்மால் எழுத்துகள் மட்டுமே"
+ },
+ "mainnet": {
+ "message": "முதன்மை எதெரியும் நெட்வொர்க்"
+ },
+ "message": {
+ "message": "செய்தி"
+ },
+ "metamaskDescription": {
+ "message": "மேடமஸ்க் என்பது ஒரு பாதுகாப்பான அடையாள வால்ட் எதெரியும்"
+ },
+ "min": {
+ "message": "குறைந்தபட்ச"
+ },
+ "myAccounts": {
+ "message": "எனது கணக்குகள்"
+ },
+ "mustSelectOne": {
+ "message": "குறைந்தது 1 டோக்கனை தேர்ந்தெடுக்க வேண்டும்."
+ },
+ "needEtherInWallet": {
+ "message": "மேடமஸ்க் ஐ பயன்படுத்தி பரவலாக்கப்பட்ட பயன்பாடுகளுடன் தொடர்பு கொள்ள, உங்கள் பணப்பரிமாற்றத்தில் ஈதர் தேவை."
+ },
+ "needImportFile": {
+ "message": "இறக்குமதி செய்ய ஒரு கோப்பை நீங்கள் தேர்ந்தெடுக்க வேண்டும்.",
+ "description": "பயனர் ஒரு கணக்கு முக்கியம் மற்றும் தொடர ஒரு கோப்பு சேர்க்க வேண்டும்"
+ },
+ "needImportPassword": {
+ "message": "நீங்கள் தேர்ந்தெடுத்த கோப்புக்கு ஒரு கடவுச்சொல்லை உள்ளிட வேண்டும்.",
+ "description": "ஒரு கணக்கை இறக்குமதி செய்ய கடவுச்சொல் மற்றும் கோப்பு தேவை"
+ },
+ "negativeETH": {
+ "message": "ETH எதிர்மறை அளவுகளை அனுப்ப முடியாது."
+ },
+ "networks": {
+ "message": "நெட்வொர்க்ஸ்"
+ },
+ "newAccount": {
+ "message": "புதிய கணக்கு"
+ },
+ "newAccountNumberName": {
+ "message": "கணக்கு $ 1",
+ "description": "கணக்கு கணக்கை உருவாக்குவதற்கு அடுத்த கணக்கின் இயல்புநிலை பெயர் உருவாக்கப்படும்"
+ },
+ "newContract": {
+ "message": "புதிய ஒப்பந்தம்"
+ },
+ "newPassword": {
+ "message": "புதிய கடவுச்சொல் (min 8 எழுத்துகள்)"
+ },
+ "newRecipient": {
+ "message": "புதிய பெறுநர்"
+ },
+ "newRPC": {
+ "message": "புதிய RPC URL"
+ },
+ "next": {
+ "message": "அடுத்த"
+ },
+ "noAddressForName": {
+ "message": "இந்த பெயருக்கான முகவரி அமைக்கப்படவில்லை."
+ },
+ "noDeposits": {
+ "message": "எந்த வைப்புகளும் கிடைக்கவில்லை"
+ },
+ "noTransactionHistory": {
+ "message": "பரிவர்த்தனை வரலாறு இல்லை."
+ },
+ "noTransactions": {
+ "message": "பரிவர்த்தனைகள் இல்லை"
+ },
+ "notStarted": {
+ "message": "துவங்கவில்லை"
+ },
+ "oldUI": {
+ "message": "பழைய UI"
+ },
+ "oldUIMessage": {
+ "message": "நீங்கள் பழைய UI க்கு திரும்பியுள்ளீர்கள். மேல் வலது கீழ்தோன்றும் மெனுவில் உள்ள விருப்பத்தின் மூலம் புதிய UI ஐ மீண்டும் மாறலாம்."
+ },
+ "or": {
+ "message": "அல்லது",
+ "description": "ஒரு புதிய கணக்கை உருவாக்க அல்லது இறக்குமதி செய்வதற்கு இடையே தேர்வு"
+ },
+ "passwordCorrect": {
+ "message": "தயவுசெய்து உங்கள் கடவுச்சொல் சரியானதா என உறுதிப்படுத்தவும்."
+ },
+ "passwordMismatch": {
+ "message": "கடவுச்சொற்கள் பொருந்தவில்லை",
+ "description": "கடவுச்சொல் உருவாக்கத்தில், இரண்டு புதிய கடவுச்சொல் புலங்கள் பொருந்தவில்லை"
+ },
+ "passwordShort": {
+ "message": "கடவுச்சொல் நீண்ட காலமாக இல்லை",
+ "description": "கடவுச்சொல் உருவாக்கத்தில், பாதுகாப்பானதாக இருக்கும் கடவுச்சொல் போதும்"
+ },
+ "pastePrivateKey": {
+ "message": "இங்கே உங்கள் தனிப்பட்ட விசை சரத்தை ஒட்டுக:",
+ "description": "ஒரு தனிப்பட்ட விசை ஒரு கணக்கை இறக்குமதி செய்ய"
+ },
+ "pasteSeed": {
+ "message": "இங்கே உங்கள் விதை சொற்றொடரை ஒட்டவும்!"
+ },
+ "personalAddressDetected": {
+ "message": "தனிப்பட்ட முகவரி கண்டறியப்பட்டது. டோக்கன் ஒப்பந்த முகவரியை உள்ளிடவும்."
+ },
+ "pleaseReviewTransaction": {
+ "message": "உங்கள் பரிவர்த்தனை மதிப்பாய்வு செய்யவும்."
+ },
+ "popularTokens": {
+ "message": "பிரபலமான டோக்கன்கள்"
+ },
+ "privacyMsg": {
+ "message": "தனியுரிமை கொள்கை"
+ },
+ "privateKey": {
+ "message": "தனிப்பட்ட விசை",
+ "description": "ஒரு கணக்கை இறக்குமதி செய்ய பயன்படுத்த இந்த வகை கோப்பை தேர்ந்தெடுக்கவும்"
+ },
+ "privateKeyWarning": {
+ "message": "எச்சரிக்கை: இந்த விசையை எப்போதும் வெளியிட வேண்டாம். உங்கள் தனிப்பட்ட விசைகளைக் கொண்ட எவரும் உங்கள் கணக்கில் உள்ள எந்த சொத்துக்களையும் திருடலாம்."
+ },
+ "privateNetwork": {
+ "message": "தனியார் நெட்வொர்க்"
+ },
+ "qrCode": {
+ "message": "QR குறியீட்டைக் காட்டு"
+ },
+ "readdToken": {
+ "message": "உங்கள் கணக்கு விருப்பங்கள் மெனுவில் \"டோக்கனைச் சேர்\" என்பதன் மூலம் நீங்கள் எதிர்காலத்தில் இந்த டோக்கனை மீண்டும் சேர்க்கலாம்."
+ },
+ "readMore": {
+ "message": "மேலும் வாசிக்க இங்கே."
+ },
+ "readMore2": {
+ "message": "மேலும் வாசிக்க."
+ },
+ "receive": {
+ "message": "பெறுக"
+ },
+ "recipientAddress": {
+ "message": "பெறுநர் முகவரி"
+ },
+ "refundAddress": {
+ "message": "உங்கள் பணத்தை திருப்பி அனுப்பும் முகவரி"
+ },
+ "rejected": {
+ "message": "நிராகரிக்கப்பட்டது"
+ },
+ "resetAccount": {
+ "message": "கணக்கை மீட்டமை"
+ },
+ "restoreFromSeed": {
+ "message": "விதை வாக்கியத்திலிருந்து மீட்கவும்"
+ },
+ "restoreVault": {
+ "message": "வால்ட் மீட்கவும்"
+ },
+ "required": {
+ "message": "தேவையான"
+ },
+ "retryWithMoreGas": {
+ "message": "இங்கே அதிக எரிவாயு விலை மீண்டும் முயற்சிக்கவும்"
+ },
+ "walletSeed": {
+ "message": "வால்ட் விதை"
+ },
+ "revealSeedWords": {
+ "message": "விதை வார்த்தைகள் வெளிப்படுத்த"
+ },
+ "revealSeedWordsWarning": {
+ "message": "உங்கள் விதை வார்த்தைகள் ஒரு பொது இடத்தில் மீட்க வேண்டாம்! உங்கள் எல்லா கணக்குகளையும் திருட இந்த வார்த்தைகள் பயன்படுத்தப்படலாம்."
+ },
+ "revert": {
+ "message": "மாற்றியமை"
+ },
+ "rinkeby": {
+ "message": "ரிங்கெப்ய டெஸ்ட் நெட்வொர்க்"
+ },
+ "ropsten": {
+ "message": "ரொப்ஸ்டென் டெஸ்ட் நெட்வொர்க்"
+ },
+ "currentRpc": {
+ "message": "தற்போதைய RPC"
+ },
+ "connectingToMainnet": {
+ "message": "முக்கிய எதெரியும் நெட்வொர்க் இணைக்கும்"
+ },
+ "connectingToRopsten": {
+ "message": "ரொப்ஸ்டென் டெஸ்ட் நெட்வொர்க்குடன் இணைக்கிறது"
+ },
+ "connectingToKovan": {
+ "message": "கோவன் டெஸ்ட் நெட்வொர்க்குடன் இணைத்தல்"
+ },
+ "connectingToRinkeby": {
+ "message": "ரிங்கெப்ய டெஸ்ட் நெட்வொர்க்குடன் இணைக்கிறது"
+ },
+ "connectingToUnknown": {
+ "message": "தெரியாத நெட்வொர்க்குடன் இணைக்கிறது"
+ },
+ "sampleAccountName": {
+ "message": "உதாரணமாக எனது புதிய கணக்கு",
+ "description": "தங்கள் கணக்கில் மனிதர் படிக்கக்கூடிய பெயரைச் சேர்க்கும் கருத்தை பயனர் புரிந்து கொள்ள உதவுங்கள்"
+ },
+ "save": {
+ "message": "சேமி"
+ },
+ "reprice_title": {
+ "message": "ரெப்ரிஸ் பரிவர்த்தனை"
+ },
+ "reprice_subtitle": {
+ "message": "உங்கள் பரிவர்த்தனைகளை மேலெழுதும் முயற்சியை அதிகரிக்க உங்கள் எரிவாயு விலையை அதிகரிக்கவும்"
+ },
+ "saveAsFile": {
+ "message": "கோப்பாக சேமிக்கவும்",
+ "description": "கணக்கு ஏற்றுமதி செயல்முறை"
+ },
+ "saveSeedAsFile": {
+ "message": "கோப்பு என விதை வார்த்தைகள் சேமிக்கவும்"
+ },
+ "search": {
+ "message": "தேடல்"
+ },
+ "secretPhrase": {
+ "message": "உங்கள் பெட்டகத்தை மீட்டெடுப்பதற்காக இங்கே உங்கள் ரகசிய பன்னிரண்டு வார்த்தை சொற்றொடரை உள்ளிடவும்."
+ },
+ "newPassword8Chars": {
+ "message": "புதிய கடவுச்சொல் (குறைந்தபட்சம் 8 எழுத்துகள்)"
+ },
+ "seedPhraseReq": {
+ "message": "விதை வாக்கியங்கள் 12 வார்த்தைகள் நீண்டவை"
+ },
+ "select": {
+ "message": "தேர்வு"
+ },
+ "selectCurrency": {
+ "message": "நாணயத்தைத் தேர்ந்தெடு"
+ },
+ "selectService": {
+ "message": "சேவை தேர்ந்தெடுக்கவும்"
+ },
+ "selectType": {
+ "message": "வகை தேர்ந்தெடு"
+ },
+ "send": {
+ "message": "அனுப்பு"
+ },
+ "sendETH": {
+ "message": "ETH ஐ அனுப்பு"
+ },
+ "sendTokens": {
+ "message": "டோக்கன்களை அனுப்பவும்"
+ },
+ "onlySendToEtherAddress": {
+ "message": "ETH ஐ ஒரு எதரியும் முகவரிக்கு மட்டும் அனுப்பவும்."
+ },
+ "searchTokens": {
+ "message": "தேடல் டோக்கன்ஸ்"
+ },
+ "sendTokensAnywhere": {
+ "message": "யாருடனும் டோக்கன்களை அனுப்பவும் எதெரியும் கணக்கு"
+ },
+ "settings": {
+ "message": "அமைப்புகள்"
+ },
+ "info": {
+ "message": "தகவல்"
+ },
+ "shapeshiftBuy": {
+ "message": "Shapeshift உடன் வாங்கவும்"
+ },
+ "showPrivateKeys": {
+ "message": "தனிப்பட்ட விசைகளைக் காண்பி"
+ },
+ "showQRCode": {
+ "message": "QR குறியீட்டைக் காட்டு"
+ },
+ "sign": {
+ "message": "உள்நுழை"
+ },
+ "signed": {
+ "message": "கையொப்பமிடப்பட்ட"
+ },
+ "signMessage": {
+ "message": "செய்தியை பதிவு செய்க"
+ },
+ "signNotice": {
+ "message": "இந்த செய்தியில் கையொப்பமிடலாம் \nஆபத்தான பக்க விளைவுகள் இருக்கலாம். \n உங்கள் மொத்த கணக்கில் முழுமையாக நம்பக்கூடிய தளங்களில் செய்திகளை மட்டுமே கையொப்பமிடுங்கள். \n இந்த ஆபத்தான முறை எதிர்கால பதிப்பில் அகற்றப்படும்."
+ },
+ "sigRequest": {
+ "message": "கையொப்பம் கோரிக்கை"
+ },
+ "sigRequested": {
+ "message": "கையொப்பம் கோரப்பட்டது"
+ },
+ "spaceBetween": {
+ "message": "வார்த்தைகள் இடையே இடைவெளி மட்டுமே இருக்க முடியும்"
+ },
+ "status": {
+ "message": "நிலைமை"
+ },
+ "stateLogs": {
+ "message": "மாநில பதிவுகள்"
+ },
+ "stateLogsDescription": {
+ "message": "மாநில பதிவுகள் உங்கள் பொது கணக்கு முகவரிகள் மற்றும் பரிமாற்றங்களை அனுப்பியுள்ளன."
+ },
+ "stateLogError": {
+ "message": "மாநில பதிவுகளை மீட்டெடுப்பதில் பிழை."
+ },
+ "submit": {
+ "message": "சமர்ப்பி"
+ },
+ "submitted": {
+ "message": "சமர்ப்பிக்கப்பட்டது"
+ },
+ "supportCenter": {
+ "message": "எங்கள் ஆதரவு மையத்தைப் பார்வையிடவும்"
+ },
+ "symbolBetweenZeroTen": {
+ "message": "குறியீடு 0 மற்றும் 10 எழுத்துகளுக்கு இடையில் இருக்க வேண்டும்."
+ },
+ "takesTooLong": {
+ "message": "நீண்ட நேரம் எடுத்துக்கொள்கிறது?"
+ },
+ "terms": {
+ "message": "பயன்பாட்டு விதிமுறைகளை"
+ },
+ "testFaucet": {
+ "message": "சோதனை குழாய்"
+ },
+ "to": {
+ "message": "பெறுநர்: "
+ },
+ "toETHviaShapeShift": {
+ "message": "$ 1 முதல் ETH வரை வடிவம்",
+ "description": "செய்தி தொடக்கத்தில் வைப்பு வகைகளில் நிரப்பப்படும்"
+ },
+ "tokenAddress": {
+ "message": "டோக்கன் முகவரி"
+ },
+ "tokenAlreadyAdded": {
+ "message": "டோக்கன் ஏற்கனவே சேர்க்கப்பட்டது."
+ },
+ "tokenBalance": {
+ "message": "உங்கள் டோக்கன் இருப்பு:"
+ },
+ "tokenSelection": {
+ "message": "டோக்கன்களைத் தேடு அல்லது பிரபல டோக்கன்களின் பட்டியலிலிருந்து தேர்ந்தெடுக்கவும்."
+ },
+ "tokenSymbol": {
+ "message": "டோக்கன் சின்னம்"
+ },
+ "tokenWarning1": {
+ "message": "உங்கள் மேடமஸ்க் கணக்குடன் நீங்கள் வாங்கிய டோக்கன்களை கண்காணியுங்கள். வேறு கணக்கைப் பயன்படுத்தி டோக்கன்களை வாங்கிவிட்டால், அந்த டோக்கன்கள் இங்கே தோன்றாது."
+ },
+ "total": {
+ "message": "மொத்த"
+ },
+ "transactions": {
+ "message": "பரிவர்த்தனைகள்"
+ },
+ "transactionError": {
+ "message": "பரிவர்த்தனை பிழை. விதிமுறை ஒப்பந்தத்தில் விதிவிலக்கு."
+ },
+ "transactionMemo": {
+ "message": "பரிவர்த்தனை குறிப்பு (விருப்பம்)"
+ },
+ "transactionNumber": {
+ "message": "பரிவர்த்தனை எண்"
+ },
+ "transfers": {
+ "message": "இடமாற்றங்கள்"
+ },
+ "troubleTokenBalances": {
+ "message": "உங்கள் டோக்கன் நிலுவைகளை ஏற்றுவதில் சிக்கல் ஏற்பட்டது. நீங்கள் அவர்களை பார்க்க முடியும்.",
+ "description": "டோக்கன் நிலுவைகளை காண ஒரு இணைப்பு (இங்கே) தொடர்ந்து"
+ },
+ "twelveWords": {
+ "message": "இந்த 12 வார்த்தைகள் உங்கள் மெட்டாமாஸ்க் கணக்கை மீட்க ஒரே வழி. \n அவற்றை எங்காவது பாதுகாப்பாகவும் ரகசியமாகவும் சேமிக்கவும்."
+ },
+ "typePassword": {
+ "message": "உங்கள் கடவுச்சொல்லை தட்டச்சு செய்யவும்"
+ },
+ "uiWelcome": {
+ "message": "புதிய UI (பீட்டா) க்கு வரவேற்கிறோம்"
+ },
+ "uiWelcomeMessage": {
+ "message": "இப்போது நீங்கள் புதிய மெட்டாமாஸ்க்கு UI ஐ பயன்படுத்துகிறீர்கள். சுற்றி பாருங்கள், டோக்கன்களை அனுப்பும் புதிய அம்சங்களை முயற்சிக்கவும், உங்களிடம் ஏதேனும் சிக்கல் இருந்தால் எங்களுக்குத் தெரியப்படுத்தவும்."
+ },
+ "unapproved": {
+ "message": "அங்கீகரிக்கப்படாத"
+ },
+ "unavailable": {
+ "message": "கிடைக்கவில்லை"
+ },
+ "unknown": {
+ "message": "தெரியாத"
+ },
+ "unknownNetwork": {
+ "message": "அறியப்படாத தனியார் நெட்வொர்க்"
+ },
+ "unknownNetworkId": {
+ "message": "தெரியாத நெட்வொர்க் ஐடி"
+ },
+ "uriErrorMsg": {
+ "message": "URI கள் சரியான HTTP / HTTPS முன்னொட்டு தேவை."
+ },
+ "usaOnly": {
+ "message": "அமெரிக்கா மட்டும்",
+ "description": "இந்த பரிமாற்றத்தைப் பயன்படுத்தி அமெரிக்காவில் உள்ளவர்களுக்கு மட்டுமே இது வரையறுக்கப்படுகிறது"
+ },
+ "usedByClients": {
+ "message": "பல்வேறு வாடிக்கையாளர்கள் பல்வேறு பயன்படுத்திய"
+ },
+ "useOldUI": {
+ "message": "உஸ் ஓல்ட் உய் "
+ },
+ "validFileImport": {
+ "message": "இறக்குமதி செய்ய சரியான கோப்பு தேர்ந்தெடுக்க வேண்டும்."
+ },
+ "vaultCreated": {
+ "message": "வால்ட் உருவாக்கப்பட்டது"
+ },
+ "viewAccount": {
+ "message": "கணக்கைக் காட்டு"
+ },
+ "visitWebSite": {
+ "message": "எங்கள் வலைத்தளத்தைப் பார்வையிடவும்"
+ },
+ "warning": {
+ "message": "எச்சரிக்கை"
+ },
+ "welcomeBeta": {
+ "message": "மெட்டாமாஸ்க் பீட்டாவுக்கு வருக"
+ },
+ "whatsThis": {
+ "message": "இது என்ன?"
+ },
+ "yourSigRequested": {
+ "message": "உங்கள் கையொப்பம் கோரப்படுகிறது"
+ },
+ "youSign": {
+ "message": "நீங்கள் கையெழுத்திடுகிறீர்கள்"
+ }
+}
diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json
new file mode 100644
index 000000000..c2c09bc91
--- /dev/null
+++ b/app/_locales/tr/messages.json
@@ -0,0 +1,912 @@
+{
+ "accept": {
+ "message": "Kabul et"
+ },
+ "account": {
+ "message": "Hesap"
+ },
+ "accountDetails": {
+ "message": "Hesap Detayları"
+ },
+ "accountName": {
+ "message": "Hesap İsmi"
+ },
+ "address": {
+ "message": "Adres"
+ },
+ "addCustomToken": {
+ "message": "Özel jeton ekle"
+ },
+ "addToken": {
+ "message": "Jeton ekle"
+ },
+ "addTokens": {
+ "message": "Jetonlar ekle"
+ },
+ "amount": {
+ "message": "Tutar"
+ },
+ "amountPlusGas": {
+ "message": "Tutar + Gas"
+ },
+ "appDescription": {
+ "message": "Ethereum Tarayıcı Uzantısı",
+ "description": "Uygulama açıklaması"
+ },
+ "appName": {
+ "message": "MetaMask",
+ "description": "Uygulama ismi"
+ },
+ "approved": {
+ "message": "Onaylandı"
+ },
+ "attemptingConnect": {
+ "message": "Blockchain'e bağlanmayı deniyor"
+ },
+ "attributions": {
+ "message": "Atıflar"
+ },
+ "available": {
+ "message": "Müsait"
+ },
+ "back": {
+ "message": "Geri"
+ },
+ "balance": {
+ "message": "Bakiye:"
+ },
+ "balances": {
+ "message": "Jeton bakiyesi"
+ },
+ "balanceIsInsufficientGas": {
+ "message": "Toplam gas için yetersiz bakiye"
+ },
+ "beta": {
+ "message": "BETA"
+ },
+ "betweenMinAndMax": {
+ "message": "$1'e eşit veya daha büyük olmalı ve $2'den küçük veya eşit olmalı",
+ "description": "Onaltılık sayının ondalık sayı olarak girişi için yardımcı"
+ },
+ "blockiesIdenticon": {
+ "message": "Blockies Identicon kullan"
+ },
+ "borrowDharma": {
+ "message": "Dharma (Beta) ile ödünç al"
+ },
+ "builtInCalifornia": {
+ "message": "MetaMask California'da tasarlandı ve yaratıldı"
+ },
+ "buy": {
+ "message": "Satın al"
+ },
+ "buyCoinbase": {
+ "message": "Coinbase'de satın al"
+ },
+ "buyCoinbaseExplainer": {
+ "message": "Coinbase bitcoin, ethereum, and litecoin alıp satmanın dünyadaki en popüler yolu"
+ },
+ "ok": {
+ "message": "Tamam"
+ },
+ "cancel": {
+ "message": "Vazgeç"
+ },
+ "classicInterface": {
+ "message": "Klasik arayüzü kullan"
+ },
+ "clickCopy": {
+ "message": "Kopyalamak için tıkla"
+ },
+ "confirm": {
+ "message": "Onayla"
+ },
+ "confirmed": {
+ "message": "Onaylandı"
+ },
+ "confirmContract": {
+ "message": "Sözleşmeyi onayla"
+ },
+ "confirmPassword": {
+ "message": "Şifreyi onayla"
+ },
+ "confirmTransaction": {
+ "message": "İşlemi onayla"
+ },
+ "continue": {
+ "message": "Devam et"
+ },
+ "continueToCoinbase": {
+ "message": "Coinbase'e devam et"
+ },
+ "contractDeployment": {
+ "message": "Sözleşme kurulumu"
+ },
+ "conversionProgress": {
+ "message": "Çevirim devam ediyor"
+ },
+ "copiedButton": {
+ "message": "Kopyalandı"
+ },
+ "copiedClipboard": {
+ "message": "Panoya kopyalandı"
+ },
+ "copiedExclamation": {
+ "message": "Kopyalandı!"
+ },
+ "copiedSafe": {
+ "message": "Güvenli bir yere kopyaladım"
+ },
+ "copy": {
+ "message": "Kopyala"
+ },
+ "copyToClipboard": {
+ "message": "Panoya kopyala"
+ },
+ "copyButton": {
+ "message": " Kopyala "
+ },
+ "copyPrivateKey": {
+ "message": "Bu sizin özel anahtarınız (kopyalamak için tıklayın)"
+ },
+ "create": {
+ "message": "Yarat"
+ },
+ "createAccount": {
+ "message": "Hesap Oluştur"
+ },
+ "createDen": {
+ "message": "Yarat"
+ },
+ "crypto": {
+ "message": "Kripto",
+ "description": "Kambiyo tipi (kripto para)"
+ },
+ "currentConversion": {
+ "message": "Geçerli çevirme"
+ },
+ "currentNetwork": {
+ "message": "Geçerli Ağ"
+ },
+ "customGas": {
+ "message": "Gas'i özelleştir"
+ },
+ "customToken": {
+ "message": "Özel Jeton"
+ },
+ "customize": {
+ "message": "Özelleştir"
+ },
+ "customRPC": {
+ "message": "Özel RPC"
+ },
+ "decimalsMustZerotoTen": {
+ "message": "Ondalıklar en azından 0 olmalı ve 36'dan büyük olmamalı."
+ },
+ "decimal": {
+ "message": "Ondalık hassasiyeti"
+ },
+ "defaultNetwork": {
+ "message": "Ether işlemleri için varsayılan ağ Main Net."
+ },
+ "denExplainer": {
+ "message": "DEN'iniz MetaMask içersinde parola-şifrelenmiş deponuzdur."
+ },
+ "deposit": {
+ "message": "Yatır"
+ },
+ "depositBTC": {
+ "message": "BTC'inizi aşağıdaki adrese yatırın:"
+ },
+ "depositCoin": {
+ "message": "$1'nızı aşağıdaki adrese yatırın",
+ "description": "Kullanıcıya hangi jetonu seçtiyse onu yatırmasını shapeshift ile söyler."
+ },
+ "depositEth": {
+ "message": "Eth yatır"
+ },
+ "depositEther": {
+ "message": "Ether yatır"
+ },
+ "depositFiat": {
+ "message": "Para yatır"
+ },
+ "depositFromAccount": {
+ "message": "Başka bir hesaptan yatır"
+ },
+ "depositShapeShift": {
+ "message": "ShapeShift ile yatır"
+ },
+ "depositShapeShiftExplainer": {
+ "message": "Eğer başka kripto paralara sahipseniz, MetaMask cüzdanınıza direk olarak Ether yatırabilirsiniz. Hesaba gerek yoktur."
+ },
+ "details": {
+ "message": "Ayrıntılar"
+ },
+ "directDeposit": {
+ "message": "Direk Yatırma"
+ },
+ "directDepositEther": {
+ "message": "Direk Ether Yatırma"
+ },
+ "directDepositEtherExplainer": {
+ "message": "Eğer çoktan Etheriniz varsa, yeni hesabınıza Ether aktarmanın en kolay yolu direk yatırmadır."
+ },
+ "done": {
+ "message": "Bitti"
+ },
+ "downloadStateLogs": {
+ "message": "Durum kayıtlarını indir"
+ },
+ "dropped": {
+ "message": "Bırakıldı"
+ },
+ "edit": {
+ "message": "Düzenle"
+ },
+ "editAccountName": {
+ "message": "Hesap ismini düzenle"
+ },
+ "emailUs": {
+ "message": "Bize e-posta atın!"
+ },
+ "encryptNewDen": {
+ "message": "Yeni DEN'inizi şifreleyin"
+ },
+ "enterPassword": {
+ "message": "Parolanızı girin"
+ },
+ "enterPasswordConfirm": {
+ "message": "Onaylamak için parolanızı girin"
+ },
+ "passwordNotLongEnough": {
+ "message": "Parola yeterince uzun değil"
+ },
+ "passwordsDontMatch": {
+ "message": "Parolalar eşleşmiyor"
+ },
+ "etherscanView": {
+ "message": "Hesabı Etherscan üzerinde izle"
+ },
+ "exchangeRate": {
+ "message": "Döviz kuru"
+ },
+ "exportPrivateKey": {
+ "message": "Özel anahtarı ver"
+ },
+ "exportPrivateKeyWarning": {
+ "message": "Özel anahtarınızı vermek sizin sorumluluğunuzdadır."
+ },
+ "failed": {
+ "message": "Başarısız oldu"
+ },
+ "fiat": {
+ "message": "Para",
+ "description": "Döviz türü"
+ },
+ "fileImportFail": {
+ "message": "Dosya alma çalışmıyor mu? Buraya tıklayın!",
+ "description": "Kullanıcıların hesaplarını JSON dosyasından almalarına yardım eder"
+ },
+ "followTwitter": {
+ "message": "Bizi twitter'da takip edin"
+ },
+ "from": {
+ "message": "Kimden"
+ },
+ "fromToSame": {
+ "message": "Kimden ve kime adresi aynı olamaz"
+ },
+ "fromShapeShift": {
+ "message": "ShapeShift'den"
+ },
+ "gas": {
+ "message": "Gas",
+ "description": "Gas maliyetinin kısa indikatörü"
+ },
+ "gasFee": {
+ "message": "Gas Ücreti"
+ },
+ "gasLimit": {
+ "message": "Gas Limiti"
+ },
+ "gasLimitCalculation": {
+ "message": "Önerilen gas limitini ağ başarı oranını baz alarak hesaplıyoruz."
+ },
+ "gasLimitRequired": {
+ "message": "Gas limiti gereklidir"
+ },
+ "gasLimitTooLow": {
+ "message": "Gas limiti en az 21000 olmalıdır"
+ },
+ "generatingSeed": {
+ "message": "Kaynak Oluşturuyor..."
+ },
+ "gasPrice": {
+ "message": "Gas Fiyatı (GWEI)"
+ },
+ "gasPriceCalculation": {
+ "message": "Önerilen gas fiyatını ağ başarı oranını baz alarak hesaplıyoruz."
+ },
+ "gasPriceRequired": {
+ "message": "Gas Fiyatı Gereklidir"
+ },
+ "getEther": {
+ "message": "Ether Al"
+ },
+ "getEtherFromFaucet": {
+ "message": "Musluktan $1 karşılığı Ether alın",
+ "description": "Ether musluğunun ağ ismini gösterir"
+ },
+ "greaterThanMin": {
+ "message": "must be greater than or equal to $1.",
+ "description": "helper for inputting hex as decimal input"
+ },
+ "here": {
+ "message": "burada",
+ "description": "daha fazla bilgi için -buraya tıklayın- (troubleTokenBalances ile gidiyor)"
+ },
+ "hereList": {
+ "message": "İşte bir liste!!!!"
+ },
+ "hide": {
+ "message": "Gizle"
+ },
+ "hideToken": {
+ "message": "Jetonu gizle"
+ },
+ "hideTokenPrompt": {
+ "message": "Jetonu gizle?"
+ },
+ "howToDeposit": {
+ "message": "Ether'i nasıl yatırmak istersiniz?"
+ },
+ "holdEther": {
+ "message": "Ether ve jeton tutmanızı sağlar ve merkezi olmayan uygulamalar ve sizin aranızda köprü vazifesi görür."
+ },
+ "import": {
+ "message": "Al",
+ "description": "Seçilen dosyadan hesap alma düğmesi. "
+ },
+ "importAccount": {
+ "message": "Hesap Al"
+ },
+ "importAccountMsg": {
+ "message":" Alınan hesaplar orjinal kaynakifadenizle yarattığınız MetaMask hesabınızla ilişkilendirilmez. Alınan hesaplar ile ilgili daha fazla bilgi edinin "
+ },
+ "importAnAccount": {
+ "message": "Hesap al"
+ },
+ "importDen": {
+ "message": "Varolan DEN al"
+ },
+ "imported": {
+ "message": "Alındı",
+ "description": "Hesabın keyringe başarı ile alındığını gösteren durum"
+ },
+ "infoHelp": {
+ "message": "Bilgi ve yardım"
+ },
+ "insufficientFunds": {
+ "message": "Yetersiz kaynak."
+ },
+ "insufficientTokens": {
+ "message": "Yetersiz Jeton."
+ },
+ "invalidAddress": {
+ "message": "Geçersiz adres"
+ },
+ "invalidAddressRecipient": {
+ "message": "Alıcı adresi geçersiz"
+ },
+ "invalidGasParams": {
+ "message": "Geçersiz gas parametreleri"
+ },
+ "invalidInput": {
+ "message": "Geçersiz giriş."
+ },
+ "invalidRequest": {
+ "message": "Geçersiz istek"
+ },
+ "invalidRPC": {
+ "message": "Geçersiz RPC URI"
+ },
+ "jsonFail": {
+ "message": "Birşeyler yanlış gitti. JSON dosyanızın düzgün derlendiğinden emin olun."
+ },
+ "jsonFile": {
+ "message": "JSON Dosyası",
+ "description": "Hesap alımı için düzenle"
+ },
+ "keepTrackTokens": {
+ "message": "MetaMask hesabınızla satın aldığınız jetonların kaydını tutun."
+ },
+ "kovan": {
+ "message": "Kovan Test Ağı"
+ },
+ "knowledgeDataBase": {
+ "message": "Bilgi veritabanımızı ziyaret edin"
+ },
+ "max": {
+ "message": "Maksimum"
+ },
+ "learnMore": {
+ "message": "Daha fazla bilgi."
+ },
+ "lessThanMax": {
+ "message": "$1'den az veya eşit olmalıdır.",
+ "description": "Onaltılık sayıyı ondalık olarak girmek için yardımcı"
+ },
+ "likeToAddTokens": {
+ "message": "Bu jetonlara adres eklemek ister misiniz?"
+ },
+ "links": {
+ "message": "Bağlantılar"
+ },
+ "limit": {
+ "message": "Limit"
+ },
+ "loading": {
+ "message": "Yükleniyor..."
+ },
+ "loadingTokens": {
+ "message": "Jetonlar yükleniyor..."
+ },
+ "localhost": {
+ "message": "Localhost 8545"
+ },
+ "login": {
+ "message": "Giriş yap"
+ },
+ "logout": {
+ "message": "Çıkış"
+ },
+ "loose": {
+ "message": "Gevşek"
+ },
+ "loweCaseWords": {
+ "message": "kaynak kelimeleri sadece küçük harflerden oluşabilir."
+ },
+ "mainnet": {
+ "message": "Main Ethereum Ağı"
+ },
+ "message": {
+ "message": "Mesaj"
+ },
+ "metamaskDescription": {
+ "message": "MetaMask Ethereum için güvenli bir kimlik kasasıdır."
+ },
+ "min": {
+ "message": "Minimum"
+ },
+ "myAccounts": {
+ "message": "Hesaplarım"
+ },
+ "mustSelectOne": {
+ "message": "En az bir jeton seçilmeli"
+ },
+ "needEtherInWallet": {
+ "message": "MetaMask kullanarak merkezi olamayan uygulamalarla etkileşmek için cüzdanınızda Ether bulunmalıdır."
+ },
+ "needImportFile": {
+ "message": "Almak için bir dosya seçmelisiniz.",
+ "description": "Kullanıcı bir hesap alır ve devam etmek içinbir dosya seçmelidir."
+ },
+ "needImportPassword": {
+ "message": "Seçilen dosya için bir parola girmelisiniz.",
+ "description": "Hesap almak için parola ve dosya gerekiyor."
+ },
+ "negativeETH": {
+ "message": "Negatif ETH miktarları gönderilemez."
+ },
+ "networks": {
+ "message": "Ağlar"
+ },
+ "newAccount": {
+ "message": "Yeni Hesap"
+ },
+ "newAccountNumberName": {
+ "message": "Hesap $1",
+ "description": "Hesap yaratma ekranındaki bir sonraki hesabın varsayılan ismi"
+ },
+ "newContract": {
+ "message": "Yeni Sözleşme"
+ },
+ "newPassword": {
+ "message": "Yeni Parola (min 8 karakter)"
+ },
+ "newRecipient": {
+ "message": "Yeni alıcı"
+ },
+ "newRPC": {
+ "message": "Yeni RPC URL"
+ },
+ "next": {
+ "message": "Sonraki"
+ },
+ "noAddressForName": {
+ "message": "Bu isim için bir adres tanımlanmamış."
+ },
+ "noDeposits": {
+ "message": "Yatırma alınmadı"
+ },
+ "noTransactionHistory": {
+ "message": "İşlem geçmişi yok."
+ },
+ "noTransactions": {
+ "message": "İşlem yok"
+ },
+ "notStarted": {
+ "message": "Başlamadı"
+ },
+ "oldUI": {
+ "message": "Eski UI"
+ },
+ "oldUIMessage": {
+ "message": "Eski UI'a döndünüz. Yeni UI'a üst sağ sekme menüsündeki seçenek ile dönebilirsiniz."
+ },
+ "or": {
+ "message": "veya",
+ "description": "Yeni bir hesap yaratmak veya almak arasındaki seçim"
+ },
+ "passwordCorrect": {
+ "message": "Lütfen parolanın doğru olduğuna emin olun."
+ },
+ "passwordMismatch": {
+ "message": "parolalar eşleşmiyor",
+ "description": "parola yaratma işleminde, iki yeni parola alanı eşleşmiyor."
+ },
+ "passwordShort": {
+ "message": "parola yeterince uzun değil",
+ "description": "parola yaratma işleminde, parola güvenli olacak kadar uzun değil."
+ },
+ "pastePrivateKey": {
+ "message": "Özel anahtar dizinizi buraya yapıştırın:",
+ "description": "Özel anahtardan hesap almak için"
+ },
+ "pasteSeed": {
+ "message": "Kaynak ifadenizi buraya yapıştırın!"
+ },
+ "personalAddressDetected": {
+ "message": "Kişisel adres tespit edilidi. Jeton sözleşme adresini girin."
+ },
+ "pleaseReviewTransaction": {
+ "message": "Lütfen işleminizi gözden geçirin."
+ },
+ "popularTokens": {
+ "message": "Popüler Jetonlar"
+ },
+ "privacyMsg": {
+ "message": "Gizlilik Şartları"
+ },
+ "privateKey": {
+ "message": "Özel Anahtar",
+ "description": "Hesap alırken bu tip bir dosya seçin"
+ },
+ "privateKeyWarning": {
+ "message": "Uyarı: Bu anahtarı kimse ile paylaşmayın. Özel anahtarlarınıza sahip herkes hesaplarınızıdaki tüm varlığınızı çalabilir."
+ },
+ "privateNetwork": {
+ "message": "Özel Ağ"
+ },
+ "qrCode": {
+ "message": "QR Kodunu göster"
+ },
+ "readdToken": {
+ "message": "Gelecekte Bu jetonu hesap seçenekleri menüsünde “Jeton ekle”'ye giderek geri ekleyebilirsiniz."
+ },
+ "readMore": {
+ "message": "Burada daha fazla okuyun."
+ },
+ "readMore2": {
+ "message": "Daha fazla okuyun."
+ },
+ "receive": {
+ "message": "Al"
+ },
+ "recipientAddress": {
+ "message": "Alıcı adresi"
+ },
+ "refundAddress": {
+ "message": "İade adresiniz"
+ },
+ "rejected": {
+ "message": "Rededildi"
+ },
+ "resetAccount": {
+ "message": "Hesabı sıfıla"
+ },
+ "restoreFromSeed": {
+ "message": "Kaynak ifadeden geri getir. Restore from seed phrase"
+ },
+ "restoreVault": {
+ "message": "Kasayı geri getir"
+ },
+ "required": {
+ "message": "Gerekli"
+ },
+ "retryWithMoreGas": {
+ "message": "Daha yüksek bir gas fiyatı ile tekrar dene"
+ },
+ "walletSeed": {
+ "message": "Cüzdan Kaynağı"
+ },
+ "revealSeedWords": {
+ "message": "Kaynak kelimelerini göster"
+ },
+ "revealSeedWordsWarning": {
+ "message": "Açık bir yerde kaynak kelimeliriniz geri getirmeyin! Bu kelimeler tüm hesaplarınızı çalmak için kullanılabilir."
+ },
+ "revert": {
+ "message": "Geri döndür"
+ },
+ "rinkeby": {
+ "message": "Rinkeby Test Ağı"
+ },
+ "ropsten": {
+ "message": "Ropsten Test Ağı"
+ },
+ "currentRpc": {
+ "message": "Geçerli RPC"
+ },
+ "connectingToMainnet": {
+ "message": "Main Ethereum Ağına bağlanıyor"
+ },
+ "connectingToRopsten": {
+ "message": "Ropsten Test Ağına bağlanıyor"
+ },
+ "connectingToKovan": {
+ "message": "Kovan Test Ağına bağlanıyor"
+ },
+ "connectingToRinkeby": {
+ "message": "Rinkeby Test Ağına bağlanıyor"
+ },
+ "connectingToUnknown": {
+ "message": "Bilinmeyen Ağa bağlanıyor"
+ },
+ "sampleAccountName": {
+ "message": "E.g. Yeni hesabım",
+ "description": "Kullanıcının hesabına okunabilir isim ekleme konseptini anlamasına yardımcı olmak."
+ },
+ "save": {
+ "message": "Kaydet"
+ },
+ "reprice_title": {
+ "message": "İşlemi Yeniden Fiyatlandır"
+ },
+ "reprice_subtitle": {
+ "message": "İşlemizi hızlandırmayı denemek için gas fiyatınızı yükseltin."
+ },
+ "saveAsFile": {
+ "message": "Dosya olarak kaydet",
+ "description": "Hesap verme işlemi"
+ },
+ "saveSeedAsFile": {
+ "message": "Kaynak Kelimelerini Dosya olarak Kaydet"
+ },
+ "search": {
+ "message": "Ara"
+ },
+ "secretPhrase": {
+ "message": "Kasanızı geri getirmek için gizli 12 kelimelik ifadenizi giriniz."
+ },
+ "newPassword8Chars": {
+ "message": "Yeni Parola (minumum 8 karakter)"
+ },
+ "seedPhraseReq": {
+ "message": "Kaynak ifadeleri 12 kelimedir."
+ },
+ "select": {
+ "message": "Seç"
+ },
+ "selectCurrency": {
+ "message": "Döviz Seç"
+ },
+ "selectService": {
+ "message": "Servis Seç"
+ },
+ "selectType": {
+ "message": "Tip Seç"
+ },
+ "send": {
+ "message": "Gönder"
+ },
+ "sendETH": {
+ "message": "ETH Gönder"
+ },
+ "sendTokens": {
+ "message": "Jeton Gönder"
+ },
+ "onlySendToEtherAddress": {
+ "message": "Ethereum adresine sadece ETH gönder."
+ },
+ "searchTokens": {
+ "message": "Jeton ara"
+ },
+ "sendTokensAnywhere": {
+ "message": "Ethereum hesabı olan birine Jeton gönder"
+ },
+ "settings": {
+ "message": "Ayarlar"
+ },
+ "info": {
+ "message": "Bilgi"
+ },
+ "shapeshiftBuy": {
+ "message": "Shapeshift ile satın al"
+ },
+ "showPrivateKeys": {
+ "message": "Özel anahtarları göster"
+ },
+ "showQRCode": {
+ "message": "QR Kodu göster"
+ },
+ "sign": {
+ "message": "İmza"
+ },
+ "signed": {
+ "message": "İmzalandı"
+ },
+ "signMessage": {
+ "message": "Mesajı İmzala"
+ },
+ "signNotice": {
+ "message": "Bu mesajı imzalamanın tehlikeli \nyan etkileri olabilir. Tamamen güvendiğiniz sitelerden \ngelen mesajları hesabınızla imzalayınız.\n Bu tehlikeli metod gelecek versiyonlarda çıkarılacaktır. "
+ },
+ "sigRequest": {
+ "message": "İmza isteği"
+ },
+ "sigRequested": {
+ "message": "İmza isteniyor"
+ },
+ "spaceBetween": {
+ "message": "Kelimeler arası sadece bir boşluk olabilir."
+ },
+ "status": {
+ "message": "Durum"
+ },
+ "stateLogs": {
+ "message": "Durum Kayıtları"
+ },
+ "stateLogsDescription": {
+ "message": "Durum kayıtları açık hesap adresinizi ve gönderilen işlemleri içerir."
+ },
+ "stateLogError": {
+ "message": "Durum kayıtlarını alma hatası"
+ },
+ "submit": {
+ "message": "Gönder"
+ },
+ "submitted": {
+ "message": "Gönderildi"
+ },
+ "supportCenter": {
+ "message": "Destek merkezimizi ziyaret edin"
+ },
+ "symbolBetweenZeroTen": {
+ "message": "Sembol 0 ve 10 karakter aralığında olmalıdır."
+ },
+ "takesTooLong": {
+ "message": "Çok mu uzun sürüyor?"
+ },
+ "terms": {
+ "message": "Kullanım şartları"
+ },
+ "testFaucet": {
+ "message": "Test Musluğu"
+ },
+ "to": {
+ "message": "Kime: "
+ },
+ "toETHviaShapeShift": {
+ "message": "ShapeShift üstünden $1'dan ETH'e",
+ "description": "system will fill in deposit type in start of message"
+ },
+ "tokenAddress": {
+ "message": "Jeton Adresi"
+ },
+ "tokenAlreadyAdded": {
+ "message": "Jeton çoktan eklenmiş."
+ },
+ "tokenBalance": {
+ "message": "Jeton bakiyeniz:"
+ },
+ "tokenSelection": {
+ "message": "Jeton arayın veya popüler jeton listemizden seçin."
+ },
+ "tokenSymbol": {
+ "message": "Jeton Sembolü"
+ },
+ "tokenWarning1": {
+ "message": "MetaMask hesabınızla aldığınız jetonların kaydını tutun. Başka bir hesapla jetonlar satın aldıysanız, o jetonlar burada gözükmeyecektir."
+ },
+ "total": {
+ "message": "Toplam"
+ },
+ "transactions": {
+ "message": "işlemler"
+ },
+ "transactionError": {
+ "message": "İşlem Hatası. Sözleşme kodundan kural dışı durum fırlatıldı."
+ },
+ "transactionMemo": {
+ "message": "İşlem notu (opsiyonel)"
+ },
+ "transactionNumber": {
+ "message": "İşlem numarası"
+ },
+ "transfers": {
+ "message": "Transferler"
+ },
+ "troubleTokenBalances": {
+ "message": "Jeton bakiyelerinizi yüklerken sorun yaşadık. Buradan izleyebilirsiniz ",
+ "description": "Jeton bakiyelerini görmek için bir link (burası) ile takip ediliyor"
+ },
+ "twelveWords": {
+ "message": "MetaMask hesaplarınızı geri getirmenin tek yolu bu 12 kelimedir.\nBu kelimeleri güvenli ve gizli bir yerde saklayın."
+ },
+ "typePassword": {
+ "message": "Parolanızı girin"
+ },
+ "uiWelcome": {
+ "message": "Yeni UI (Beta)'ya hoşgeldiniz"
+ },
+ "uiWelcomeMessage": {
+ "message": "Şu anda yeni Metamask UI kullanmaktasınız. Gözatın, jeton gönderme gibi yeni özellikleri deneyin ve herhangi bir sorunlar karşılaşırsanız bize haber verin"
+ },
+ "unapproved": {
+ "message": "Onaylanmadı"
+ },
+ "unavailable": {
+ "message": "Mevcut değil"
+ },
+ "unknown": {
+ "message": "Bilinmeyen"
+ },
+ "unknownNetwork": {
+ "message": "Bilinmeyen özel ağ"
+ },
+ "unknownNetworkId": {
+ "message": "Bilinmeyen ağ IDsi"
+ },
+ "uriErrorMsg": {
+ "message": "URIler için HTTP/HTTPS öneki gerekmektedir."
+ },
+ "usaOnly": {
+ "message": "Sadece ABD",
+ "description": "Bu dövizi sadece ABD ikamet edenler kullanabilir"
+ },
+ "usedByClients": {
+ "message": "Farklı istemciler tarafından kullanılmakta"
+ },
+ "useOldUI": {
+ "message": "Eski UI kullan"
+ },
+ "validFileImport": {
+ "message": "Almak için geçerli bir dosya seçmelisiniz"
+ },
+ "vaultCreated": {
+ "message": "Kasa Yaratıldı"
+ },
+ "viewAccount": {
+ "message": "Hesabı İncele"
+ },
+ "visitWebSite": {
+ "message": "Web sitemizi ziyaret edin"
+ },
+ "warning": {
+ "message": "Uyarı"
+ },
+ "welcomeBeta": {
+ "message": "MetaMask Beta'ya Hoşgeldiniz"
+ },
+ "whatsThis": {
+ "message": "Bu nedir?"
+ },
+ "yourSigRequested": {
+ "message": "İmzanız isteniyor"
+ },
+ "youSign": {
+ "message": "İmzalıyorsunuz"
+ }
+} \ No newline at end of file
diff --git a/app/manifest.json b/app/manifest.json
index 61d2c5b5e..dc46f1ca4 100644
--- a/app/manifest.json
+++ b/app/manifest.json
@@ -1,7 +1,7 @@
{
"name": "__MSG_appName__",
"short_name": "__MSG_appName__",
- "version": "4.5.3",
+ "version": "4.5.5",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "__MSG_appDescription__",
diff --git a/app/scripts/background.js b/app/scripts/background.js
index ec586f642..38b871bb5 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -1,3 +1,7 @@
+/**
+ * @file The entry point for the web extension singleton process.
+ */
+
const urlUtil = require('url')
const endOfStream = require('end-of-stream')
const pump = require('pump')
@@ -21,12 +25,16 @@ const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
const EdgeEncryptor = require('./edge-encryptor')
const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code')
const getObjStructure = require('./lib/getObjStructure')
+const {
+ ENVIRONMENT_TYPE_POPUP,
+ ENVIRONMENT_TYPE_NOTIFICATION,
+ ENVIRONMENT_TYPE_FULLSCREEN,
+} = require('./lib/enums')
const STORAGE_KEY = 'metamask-config'
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
-window.log = log
-log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
+log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
const platform = new ExtensionPlatform()
const notificationManager = new NotificationManager()
@@ -44,7 +52,7 @@ const isEdge = !isIE && !!window.StyleMedia
let popupIsOpen = false
let notificationIsOpen = false
-let openMetamaskTabsIDs = {}
+const openMetamaskTabsIDs = {}
// state persistence
const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY })
@@ -57,6 +65,90 @@ initialize().catch(log.error)
// setup metamask mesh testing container
setupMetamaskMeshMetrics()
+/**
+ * An object representing a transaction, in whatever state it is in.
+ * @typedef TransactionMeta
+ *
+ * @property {number} id - An internally unique tx identifier.
+ * @property {number} time - Time the tx was first suggested, in unix epoch time (ms).
+ * @property {string} status - The current transaction status (unapproved, signed, submitted, dropped, failed, rejected), as defined in `tx-state-manager.js`.
+ * @property {string} metamaskNetworkId - The transaction's network ID, used for EIP-155 compliance.
+ * @property {boolean} loadingDefaults - TODO: Document
+ * @property {Object} txParams - The tx params as passed to the network provider.
+ * @property {Object[]} history - A history of mutations to this TransactionMeta object.
+ * @property {boolean} gasPriceSpecified - True if the suggesting dapp specified a gas price, prevents auto-estimation.
+ * @property {boolean} gasLimitSpecified - True if the suggesting dapp specified a gas limit, prevents auto-estimation.
+ * @property {string} estimatedGas - A hex string represented the estimated gas limit required to complete the transaction.
+ * @property {string} origin - A string representing the interface that suggested the transaction.
+ * @property {Object} nonceDetails - A metadata object containing information used to derive the suggested nonce, useful for debugging nonce issues.
+ * @property {string} rawTx - A hex string of the final signed transaction, ready to submit to the network.
+ * @property {string} hash - A hex string of the transaction hash, used to identify the transaction on the network.
+ * @property {number} submittedTime - The time the transaction was submitted to the network, in Unix epoch time (ms).
+ */
+
+/**
+ * The data emitted from the MetaMaskController.store EventEmitter, also used to initialize the MetaMaskController. Available in UI on React state as state.metamask.
+ * @typedef MetaMaskState
+ * @property {boolean} isInitialized - Whether the first vault has been created.
+ * @property {boolean} isUnlocked - Whether the vault is currently decrypted and accounts are available for selection.
+ * @property {boolean} isAccountMenuOpen - Represents whether the main account selection UI is currently displayed.
+ * @property {boolean} isMascara - True if the current context is the extensionless MetaMascara project.
+ * @property {boolean} isPopup - Returns true if the current view is an externally-triggered notification.
+ * @property {string} rpcTarget - DEPRECATED - The URL of the current RPC provider.
+ * @property {Object} identities - An object matching lower-case hex addresses to Identity objects with "address" and "name" (nickname) keys.
+ * @property {Object} unapprovedTxs - An object mapping transaction hashes to unapproved transactions.
+ * @property {boolean} noActiveNotices - False if there are notices the user should confirm before using the application.
+ * @property {Array} frequentRpcList - A list of frequently used RPCs, including custom user-provided ones.
+ * @property {Array} addressBook - A list of previously sent to addresses.
+ * @property {address} selectedTokenAddress - Used to indicate if a token is globally selected. Should be deprecated in favor of UI-centric token selection.
+ * @property {Object} tokenExchangeRates - Info about current token prices.
+ * @property {Array} tokens - Tokens held by the current user, including their balances.
+ * @property {Object} send - TODO: Document
+ * @property {Object} coinOptions - TODO: Document
+ * @property {boolean} useBlockie - Indicates preferred user identicon format. True for blockie, false for Jazzicon.
+ * @property {Object} featureFlags - An object for optional feature flags.
+ * @property {string} networkEndpointType - TODO: Document
+ * @property {boolean} isRevealingSeedWords - True if seed words are currently being recovered, and should be shown to user.
+ * @property {boolean} welcomeScreen - True if welcome screen should be shown.
+ * @property {string} currentLocale - A locale string matching the user's preferred display language.
+ * @property {Object} provider - The current selected network provider.
+ * @property {string} provider.rpcTarget - The address for the RPC API, if using an RPC API.
+ * @property {string} provider.type - An identifier for the type of network selected, allows MetaMask to use custom provider strategies for known networks.
+ * @property {string} network - A stringified number of the current network ID.
+ * @property {Object} accounts - An object mapping lower-case hex addresses to objects with "balance" and "address" keys, both storing hex string values.
+ * @property {hex} currentBlockGasLimit - The most recently seen block gas limit, in a lower case hex prefixed string.
+ * @property {TransactionMeta[]} selectedAddressTxList - An array of transactions associated with the currently selected account.
+ * @property {Object} unapprovedMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
+ * @property {number} unapprovedMsgCount - The number of messages in unapprovedMsgs.
+ * @property {Object} unapprovedPersonalMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
+ * @property {number} unapprovedPersonalMsgCount - The number of messages in unapprovedPersonalMsgs.
+ * @property {Object} unapprovedTypedMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
+ * @property {number} unapprovedTypedMsgCount - The number of messages in unapprovedTypedMsgs.
+ * @property {string[]} keyringTypes - An array of unique keyring identifying strings, representing available strategies for creating accounts.
+ * @property {Keyring[]} keyrings - An array of keyring descriptions, summarizing the accounts that are available for use, and what keyrings they belong to.
+ * @property {Object} computedBalances - Maps accounts to their balances, accounting for balance changes from pending transactions.
+ * @property {string} currentAccountTab - A view identifying string for displaying the current displayed view, allows user to have a preferred tab in the old UI (between tokens and history).
+ * @property {string} selectedAddress - A lower case hex string of the currently selected address.
+ * @property {string} currentCurrency - A string identifying the user's preferred display currency, for use in showing conversion rates.
+ * @property {number} conversionRate - A number representing the current exchange rate from the user's preferred currency to Ether.
+ * @property {number} conversionDate - A unix epoch date (ms) for the time the current conversion rate was last retrieved.
+ * @property {Object} infuraNetworkStatus - An object of infura network status checks.
+ * @property {Block[]} recentBlocks - An array of recent blocks, used to calculate an effective but cheap gas price.
+ * @property {Array} shapeShiftTxList - An array of objects describing shapeshift exchange attempts.
+ * @property {Array} lostAccounts - TODO: Remove this feature. A leftover from the version-3 migration where our seed-phrase library changed to fix a bug where some accounts were mis-generated, but we recovered the old accounts as "lost" instead of losing them.
+ * @property {boolean} forgottenPassword - Returns true if the user has initiated the password recovery screen, is recovering from seed phrase.
+ */
+
+/**
+ * @typedef VersionedData
+ * @property {MetaMaskState} data - The data emitted from MetaMask controller, or used to initialize it.
+ * @property {Number} version - The latest migration version that has been run.
+ */
+
+/**
+ * Initializes the MetaMask controller, and sets up all platform configuration.
+ * @returns {Promise} Setup complete.
+ */
async function initialize () {
const initState = await loadStateFromPersistence()
const initLangCode = await getFirstPreferredLangCode()
@@ -68,6 +160,11 @@ async function initialize () {
// State and Persistence
//
+/**
+ * Loads any stored data, prioritizing the latest storage strategy.
+ * Migrates that data schema in case it was last loaded on an older version.
+ * @returns {Promise<MetaMaskState>} Last data emitted from previous instance of MetaMask.
+ */
async function loadStateFromPersistence () {
// migrations
const migrator = new Migrator({ migrations })
@@ -78,6 +175,28 @@ async function loadStateFromPersistence () {
diskStore.getState() ||
migrator.generateInitialState(firstTimeState)
+ // check if somehow state is empty
+ // this should never happen but new error reporting suggests that it has
+ // for a small number of users
+ // https://github.com/metamask/metamask-extension/issues/3919
+ if (versionedData && !versionedData.data) {
+ // try to recover from diskStore incase only localStore is bad
+ const diskStoreState = diskStore.getState()
+ if (diskStoreState && diskStoreState.data) {
+ // we were able to recover (though it might be old)
+ versionedData = diskStoreState
+ const vaultStructure = getObjStructure(versionedData)
+ raven.captureMessage('MetaMask - Empty vault found - recovered from diskStore', {
+ // "extra" key is required by Sentry
+ extra: { vaultStructure },
+ })
+ } else {
+ // unable to recover, clear state
+ versionedData = migrator.generateInitialState(firstTimeState)
+ raven.captureMessage('MetaMask - Empty vault found - unable to recover')
+ }
+ }
+
// report migration errors to sentry
migrator.on('error', (err) => {
// get vault structure without secrets
@@ -108,6 +227,16 @@ async function loadStateFromPersistence () {
return versionedData.data
}
+/**
+ * Initializes the MetaMask Controller with any initial state and default language.
+ * Configures platform-specific error reporting strategy.
+ * Streams emitted state updates to platform-specific storage strategy.
+ * Creates platform listeners for new Dapps/Contexts, and sets up their data connections to the controller.
+ *
+ * @param {Object} initState - The initial state to start the controller with, matches the state that is emitted from the controller.
+ * @param {String} initLangCode - The region code for the language preferred by the current user.
+ * @returns {Promise} After setup is complete.
+ */
function setupController (initState, initLangCode) {
//
// MetaMask Controller
@@ -140,18 +269,29 @@ function setupController (initState, initLangCode) {
asStream(controller.store),
debounce(1000),
storeTransform(versionifyData),
- storeTransform(syncDataWithExtension),
+ storeTransform(persistData),
(error) => {
- log.error('pump hit error', error)
+ log.error('MetaMask - Persistence pipeline failed', error)
}
)
+ /**
+ * Assigns the given state to the versioned object (with metadata), and returns that.
+ * @param {Object} state - The state object as emitted by the MetaMaskController.
+ * @returns {VersionedData} The state object wrapped in an object that includes a metadata key.
+ */
function versionifyData (state) {
versionedData.data = state
return versionedData
}
- function syncDataWithExtension(state) {
+ function persistData (state) {
+ if (!state) {
+ throw new Error('MetaMask - updated state is missing', state)
+ }
+ if (!state.data) {
+ throw new Error('MetaMask - updated state does not have data', state)
+ }
if (localStore.isSupported) {
localStore.set(state)
.catch((err) => {
@@ -164,30 +304,65 @@ function setupController (initState, initLangCode) {
//
// connect to other contexts
//
-
extension.runtime.onConnect.addListener(connectRemote)
+
+ const metamaskInternalProcessHash = {
+ [ENVIRONMENT_TYPE_POPUP]: true,
+ [ENVIRONMENT_TYPE_NOTIFICATION]: true,
+ [ENVIRONMENT_TYPE_FULLSCREEN]: true,
+ }
+
+ const isClientOpenStatus = () => {
+ return popupIsOpen || Boolean(Object.keys(openMetamaskTabsIDs).length) || notificationIsOpen
+ }
+
+ /**
+ * A runtime.Port object, as provided by the browser:
+ * @link https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/Port
+ * @typedef Port
+ * @type Object
+ */
+
+ /**
+ * Connects a Port to the MetaMask controller via a multiplexed duplex stream.
+ * This method identifies trusted (MetaMask) interfaces, and connects them differently from untrusted (web pages).
+ * @param {Port} remotePort - The port provided by a new context.
+ */
function connectRemote (remotePort) {
- const isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification'
+ const processName = remotePort.name
+ const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName]
const portStream = new PortStream(remotePort)
+
if (isMetaMaskInternalProcess) {
// communication with popup
- popupIsOpen = popupIsOpen || (remotePort.name === 'popup')
+ controller.isClientOpen = true
controller.setupTrustedCommunication(portStream, 'MetaMask')
- // record popup as closed
- if (remotePort.sender.url.match(/home.html$/)) {
- openMetamaskTabsIDs[remotePort.sender.tab.id] = true
- }
- if (remotePort.name === 'popup') {
+
+ if (processName === ENVIRONMENT_TYPE_POPUP) {
+ popupIsOpen = true
+
endOfStream(portStream, () => {
popupIsOpen = false
- if (remotePort.sender.url.match(/home.html$/)) {
- openMetamaskTabsIDs[remotePort.sender.tab.id] = false
- }
+ controller.isClientOpen = isClientOpenStatus()
})
}
- if (remotePort.name === 'notification') {
+
+ if (processName === ENVIRONMENT_TYPE_NOTIFICATION) {
+ notificationIsOpen = true
+
endOfStream(portStream, () => {
notificationIsOpen = false
+ controller.isClientOpen = isClientOpenStatus()
+ })
+ }
+
+ if (processName === ENVIRONMENT_TYPE_FULLSCREEN) {
+ const tabId = remotePort.sender.tab.id
+ openMetamaskTabsIDs[tabId] = true
+
+ endOfStream(portStream, () => {
+ delete openMetamaskTabsIDs[tabId]
+ controller.isClientOpen = isClientOpenStatus()
})
}
} else {
@@ -206,7 +381,10 @@ function setupController (initState, initLangCode) {
controller.messageManager.on('updateBadge', updateBadge)
controller.personalMessageManager.on('updateBadge', updateBadge)
- // plugin badge text
+ /**
+ * Updates the Web Extension's "badge" number, on the little fox in the toolbar.
+ * The number reflects the current number of pending transactions or message signatures needing user approval.
+ */
function updateBadge () {
var label = ''
var unapprovedTxCount = controller.txController.getUnapprovedTxCount()
@@ -228,12 +406,15 @@ function setupController (initState, initLangCode) {
// Etc...
//
-// popup trigger
+/**
+ * Opens the browser popup for user confirmation
+ */
function triggerUi () {
- extension.tabs.query({ active: true }, (tabs) => {
- const currentlyActiveMetamaskTab = tabs.find(tab => openMetamaskTabsIDs[tab.id])
- if (!popupIsOpen && !currentlyActiveMetamaskTab && !notificationIsOpen) notificationManager.showPopup()
- notificationIsOpen = true
+ extension.tabs.query({ active: true }, tabs => {
+ const currentlyActiveMetamaskTab = Boolean(tabs.find(tab => openMetamaskTabsIDs[tab.id]))
+ if (!popupIsOpen && !currentlyActiveMetamaskTab && !notificationIsOpen) {
+ notificationManager.showPopup()
+ }
})
}
diff --git a/app/scripts/config.js b/app/scripts/config.js
deleted file mode 100644
index a8470ed82..000000000
--- a/app/scripts/config.js
+++ /dev/null
@@ -1,44 +0,0 @@
-const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask'
-const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask'
-const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
-const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
-const LOCALHOST_RPC_URL = 'http://localhost:8545'
-
-const MAINET_RPC_URL_BETA = 'https://mainnet.infura.io/metamask2'
-const ROPSTEN_RPC_URL_BETA = 'https://ropsten.infura.io/metamask2'
-const KOVAN_RPC_URL_BETA = 'https://kovan.infura.io/metamask2'
-const RINKEBY_RPC_URL_BETA = 'https://rinkeby.infura.io/metamask2'
-
-const DEFAULT_RPC = 'rinkeby'
-const OLD_UI_NETWORK_TYPE = 'network'
-const BETA_UI_NETWORK_TYPE = 'networkBeta'
-
-global.METAMASK_DEBUG = process.env.METAMASK_DEBUG
-
-module.exports = {
- network: {
- localhost: LOCALHOST_RPC_URL,
- mainnet: MAINET_RPC_URL,
- ropsten: ROPSTEN_RPC_URL,
- kovan: KOVAN_RPC_URL,
- rinkeby: RINKEBY_RPC_URL,
- },
- // Used for beta UI
- networkBeta: {
- localhost: LOCALHOST_RPC_URL,
- mainnet: MAINET_RPC_URL_BETA,
- ropsten: ROPSTEN_RPC_URL_BETA,
- kovan: KOVAN_RPC_URL_BETA,
- rinkeby: RINKEBY_RPC_URL_BETA,
- },
- networkNames: {
- 3: 'Ropsten',
- 4: 'Rinkeby',
- 42: 'Kovan',
- },
- enums: {
- DEFAULT_RPC,
- OLD_UI_NETWORK_TYPE,
- BETA_UI_NETWORK_TYPE,
- },
-}
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index fe1766273..dbf1c6d4c 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -23,6 +23,9 @@ if (shouldInjectWeb3()) {
setupStreams()
}
+/**
+ * Creates a script tag that injects inpage.js
+ */
function setupInjection () {
try {
// inject in-page script
@@ -37,6 +40,10 @@ function setupInjection () {
}
}
+/**
+ * Sets up two-way communication streams between the
+ * browser extension and local per-page browser context
+ */
function setupStreams () {
// setup communication to page and plugin
const pageStream = new LocalMessageDuplexStream({
@@ -89,17 +96,34 @@ function setupStreams () {
mux.ignoreStream('publicConfig')
}
+
+/**
+ * Error handler for page to plugin stream disconnections
+ *
+ * @param {string} remoteLabel Remote stream name
+ * @param {Error} err Stream connection error
+ */
function logStreamDisconnectWarning (remoteLabel, err) {
let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}`
if (err) warningMsg += '\n' + err.stack
console.warn(warningMsg)
}
+/**
+ * Determines if Web3 should be injected
+ *
+ * @returns {boolean} {@code true} if Web3 should be injected
+ */
function shouldInjectWeb3 () {
return doctypeCheck() && suffixCheck()
&& documentElementCheck() && !blacklistedDomainCheck()
}
+/**
+ * Checks the doctype of the current document if it exists
+ *
+ * @returns {boolean} {@code true} if the doctype is html or if none exists
+ */
function doctypeCheck () {
const doctype = window.document.doctype
if (doctype) {
@@ -109,6 +133,11 @@ function doctypeCheck () {
}
}
+/**
+ * Checks the current document extension
+ *
+ * @returns {boolean} {@code true} if the current extension is not prohibited
+ */
function suffixCheck () {
var prohibitedTypes = ['xml', 'pdf']
var currentUrl = window.location.href
@@ -122,6 +151,11 @@ function suffixCheck () {
return true
}
+/**
+ * Checks the documentElement of the current document
+ *
+ * @returns {boolean} {@code true} if the documentElement is an html node or if none exists
+ */
function documentElementCheck () {
var documentElement = document.documentElement.nodeName
if (documentElement) {
@@ -130,6 +164,11 @@ function documentElementCheck () {
return true
}
+/**
+ * Checks if the current domain is blacklisted
+ *
+ * @returns {boolean} {@code true} if the current domain is blacklisted
+ */
function blacklistedDomainCheck () {
var blacklistedDomains = [
'uscourts.gov',
@@ -148,6 +187,9 @@ function blacklistedDomainCheck () {
return false
}
+/**
+ * Redirects the current page to a phishing information page
+ */
function redirectToPhishingWarning () {
console.log('MetaMask - redirecting to phishing warning')
window.location.href = 'https://metamask.io/phishing.html'
diff --git a/app/scripts/controllers/address-book.js b/app/scripts/controllers/address-book.js
index 6fb4ee114..c91e6b2e4 100644
--- a/app/scripts/controllers/address-book.js
+++ b/app/scripts/controllers/address-book.js
@@ -4,9 +4,22 @@ const extend = require('xtend')
class AddressBookController {
- // Controller in charge of managing the address book functionality from the
- // recipients field on the send screen. Manages a history of all saved
- // addresses and all currently owned addresses.
+ /**
+ * Controller in charge of managing the address book functionality from the
+ * recipients field on the send screen. Manages a history of all saved
+ * addresses and all currently owned addresses.
+ *
+ * @typedef {Object} AddressBookController
+ * @param {object} opts Overrides the defaults for the initial state of this.store
+ * @property {array} opts.initState initializes the the state of the AddressBookController. Can contain an
+ * addressBook property to initialize the addressBook array
+ * @param {KeyringController} keyringController (Soon to be deprecated) The keyringController used in the current
+ * MetamaskController. Contains the identities used in this AddressBookController.
+ * @property {object} store The the store of the current users address book
+ * @property {array} store.addressBook An array of addresses and nicknames. These are set by the user when sending
+ * to a new address.
+ *
+ */
constructor (opts = {}, keyringController) {
const initState = extend({
addressBook: [],
@@ -19,7 +32,14 @@ class AddressBookController {
// PUBLIC METHODS
//
- // Sets a new address book in store by accepting a new address and nickname.
+ /**
+ * Sets a new address book in store by accepting a new address and nickname.
+ *
+ * @param {string} address A hex address of a new account that the user is sending to.
+ * @param {string} name The name the user wishes to associate with the new account
+ * @returns {Promise<void>} Promise resolves with undefined
+ *
+ */
setAddressBook (address, name) {
return this._addToAddressBook(address, name)
.then((addressBook) => {
@@ -30,14 +50,16 @@ class AddressBookController {
})
}
- //
- // PRIVATE METHODS
- //
-
-
- // Performs the logic to add the address and name into the address book. The
- // pushed object is an object of two fields. Current behavior does not set an
- // upper limit to the number of addresses.
+ /**
+ * Performs the logic to add the address and name into the address book. The pushed object is an object of two
+ * fields. Current behavior does not set an upper limit to the number of addresses.
+ *
+ * @private
+ * @param {string} address A hex address of a new account that the user is sending to.
+ * @param {string} name The name the user wishes to associate with the new account
+ * @returns {Promise<array>} Promises the updated addressBook array
+ *
+ */
_addToAddressBook (address, name) {
const addressBook = this._getAddressBook()
const identities = this._getIdentities()
@@ -62,14 +84,26 @@ class AddressBookController {
return Promise.resolve(addressBook)
}
- // Internal method to get the address book. Current persistence behavior
- // should not require that this method be called from the UI directly.
+ /**
+ * Internal method to get the address book. Current persistence behavior should not require that this method be
+ * called from the UI directly.
+ *
+ * @private
+ * @returns {array} The addressBook array from the store.
+ *
+ */
_getAddressBook () {
return this.store.getState().addressBook
}
- // Retrieves identities from the keyring controller in order to avoid
- // duplication
+ /**
+ * Retrieves identities from the keyring controller in order to avoid
+ * duplication
+ *
+ * @deprecated
+ * @returns {array} Returns the identies array from the keyringContoller's state
+ *
+ */
_getIdentities () {
return this.keyringController.memStore.getState().identities
}
diff --git a/app/scripts/controllers/blacklist.js b/app/scripts/controllers/blacklist.js
index df41c90c0..d965f80b8 100644
--- a/app/scripts/controllers/blacklist.js
+++ b/app/scripts/controllers/blacklist.js
@@ -1,6 +1,7 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
const PhishingDetector = require('eth-phishing-detect/src/detector')
+const log = require('loglevel')
// compute phishing lists
const PHISHING_DETECTION_CONFIG = require('eth-phishing-detect/src/config.json')
diff --git a/app/scripts/controllers/computed-balances.js b/app/scripts/controllers/computed-balances.js
index 907b087cf..1a6802f9a 100644
--- a/app/scripts/controllers/computed-balances.js
+++ b/app/scripts/controllers/computed-balances.js
@@ -2,8 +2,24 @@ const ObservableStore = require('obs-store')
const extend = require('xtend')
const BalanceController = require('./balance')
-class ComputedbalancesController {
+/**
+ * @typedef {Object} ComputedBalancesOptions
+ * @property {Object} accountTracker Account tracker store reference
+ * @property {Object} txController Token controller reference
+ * @property {Object} blockTracker Block tracker reference
+ * @property {Object} initState Initial state to populate this internal store with
+ */
+/**
+ * Background controller responsible for syncing
+ * and computing ETH balances for all accounts
+ */
+class ComputedbalancesController {
+ /**
+ * Creates a new controller instance
+ *
+ * @param {ComputedBalancesOptions} [opts] Controller configuration parameters
+ */
constructor (opts = {}) {
const { accountTracker, txController, blockTracker } = opts
this.accountTracker = accountTracker
@@ -19,6 +35,9 @@ class ComputedbalancesController {
this._initBalanceUpdating()
}
+ /**
+ * Updates balances associated with each internal address
+ */
updateAllBalances () {
Object.keys(this.balances).forEach((balance) => {
const address = balance.address
@@ -26,12 +45,23 @@ class ComputedbalancesController {
})
}
+ /**
+ * Initializes internal address tracking
+ *
+ * @private
+ */
_initBalanceUpdating () {
const store = this.accountTracker.store.getState()
this.syncAllAccountsFromStore(store)
this.accountTracker.store.subscribe(this.syncAllAccountsFromStore.bind(this))
}
+ /**
+ * Uses current account state to sync and track all
+ * addresses associated with the current account
+ *
+ * @param {{ accounts: Object }} store Account tracking state
+ */
syncAllAccountsFromStore (store) {
const upstream = Object.keys(store.accounts)
const balances = Object.keys(this.balances)
@@ -50,6 +80,13 @@ class ComputedbalancesController {
})
}
+ /**
+ * Conditionally establishes a new subscription
+ * to track an address associated with the current
+ * account
+ *
+ * @param {string} address Address to conditionally subscribe to
+ */
trackAddressIfNotAlready (address) {
const state = this.store.getState()
if (!(address in state.computedBalances)) {
@@ -57,6 +94,12 @@ class ComputedbalancesController {
}
}
+ /**
+ * Establishes a new subscription to track an
+ * address associated with the current account
+ *
+ * @param {string} address Address to conditionally subscribe to
+ */
trackAddress (address) {
const updater = new BalanceController({
address,
diff --git a/app/scripts/controllers/currency.js b/app/scripts/controllers/currency.js
index 36b8808aa..480c08b1c 100644
--- a/app/scripts/controllers/currency.js
+++ b/app/scripts/controllers/currency.js
@@ -1,11 +1,28 @@
-const ObservableStore = require('obs-store')
+ const ObservableStore = require('obs-store')
const extend = require('xtend')
+const log = require('loglevel')
// every ten minutes
const POLLING_INTERVAL = 600000
class CurrencyController {
+ /**
+ * Controller responsible for managing data associated with the currently selected currency.
+ *
+ * @typedef {Object} CurrencyController
+ * @param {object} opts Overrides the defaults for the initial state of this.store
+ * @property {array} opts.initState initializes the the state of the CurrencyController. Can contain an
+ * currentCurrency, conversionRate and conversionDate properties
+ * @property {string} currentCurrency A 2-4 character shorthand that describes a specific currency, currently
+ * selected by the user
+ * @property {number} conversionRate The conversion rate from ETH to the selected currency.
+ * @property {string} conversionDate The date at which the conversion rate was set. Expressed in in milliseconds
+ * since midnight of January 1, 1970
+ * @property {number} conversionInterval The id of the interval created by the scheduleConversionInterval method.
+ * Used to clear an existing interval on subsequent calls of that method.
+ *
+ */
constructor (opts = {}) {
const initState = extend({
currentCurrency: 'usd',
@@ -19,30 +36,73 @@ class CurrencyController {
// PUBLIC METHODS
//
+ /**
+ * A getter for the currentCurrency property
+ *
+ * @returns {string} A 2-4 character shorthand that describes a specific currency, currently selected by the user
+ *
+ */
getCurrentCurrency () {
return this.store.getState().currentCurrency
}
+ /**
+ * A setter for the currentCurrency property
+ *
+ * @param {string} currentCurrency The new currency to set as the currentCurrency in the store
+ *
+ */
setCurrentCurrency (currentCurrency) {
this.store.updateState({ currentCurrency })
}
+ /**
+ * A getter for the conversionRate property
+ *
+ * @returns {string} The conversion rate from ETH to the selected currency.
+ *
+ */
getConversionRate () {
return this.store.getState().conversionRate
}
+ /**
+ * A setter for the conversionRate property
+ *
+ * @param {number} conversionRate The new rate to set as the conversionRate in the store
+ *
+ */
setConversionRate (conversionRate) {
this.store.updateState({ conversionRate })
}
+ /**
+ * A getter for the conversionDate property
+ *
+ * @returns {string} The date at which the conversion rate was set. Expressed in milliseconds since midnight of
+ * January 1, 1970
+ *
+ */
getConversionDate () {
return this.store.getState().conversionDate
}
+ /**
+ * A setter for the conversionDate property
+ *
+ * @param {number} conversionDate The date, expressed in milliseconds since midnight of January 1, 1970, that the
+ * conversionRate was set
+ *
+ */
setConversionDate (conversionDate) {
this.store.updateState({ conversionDate })
}
+ /**
+ * Updates the conversionRate and conversionDate properties associated with the currentCurrency. Updated info is
+ * fetched from an external API
+ *
+ */
async updateConversionRate () {
let currentCurrency
try {
@@ -58,6 +118,12 @@ class CurrencyController {
}
}
+ /**
+ * Creates a new poll, using setInterval, to periodically call updateConversionRate. The id of the interval is
+ * stored at the controller's conversionInterval property. If it is called and such an id already exists, the
+ * previous interval is clear and a new one is created.
+ *
+ */
scheduleConversionInterval () {
if (this.conversionInterval) {
clearInterval(this.conversionInterval)
diff --git a/app/scripts/controllers/infura.js b/app/scripts/controllers/infura.js
index c6b4c9de2..8f6dd837e 100644
--- a/app/scripts/controllers/infura.js
+++ b/app/scripts/controllers/infura.js
@@ -1,5 +1,6 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
+const log = require('loglevel')
// every ten minutes
const POLLING_INTERVAL = 10 * 60 * 1000
diff --git a/app/scripts/controllers/network/enums.js b/app/scripts/controllers/network/enums.js
new file mode 100644
index 000000000..4f29e301b
--- /dev/null
+++ b/app/scripts/controllers/network/enums.js
@@ -0,0 +1,56 @@
+const ROPSTEN = 'ropsten'
+const RINKEBY = 'rinkeby'
+const KOVAN = 'kovan'
+const MAINNET = 'mainnet'
+const LOCALHOST = 'localhost'
+
+const ROPSTEN_CODE = 3
+const RINKEYBY_CODE = 4
+const KOVAN_CODE = 42
+
+const ROPSTEN_DISPLAY_NAME = 'Ropsten'
+const RINKEBY_DISPLAY_NAME = 'Rinkeby'
+const KOVAN_DISPLAY_NAME = 'Kovan'
+const MAINNET_DISPLAY_NAME = 'Main Ethereum Network'
+
+const MAINNET_RPC_URL = 'https://mainnet.infura.io/metamask'
+const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask'
+const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
+const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
+const LOCALHOST_RPC_URL = 'http://localhost:8545'
+
+const MAINNET_RPC_URL_BETA = 'https://mainnet.infura.io/metamask2'
+const ROPSTEN_RPC_URL_BETA = 'https://ropsten.infura.io/metamask2'
+const KOVAN_RPC_URL_BETA = 'https://kovan.infura.io/metamask2'
+const RINKEBY_RPC_URL_BETA = 'https://rinkeby.infura.io/metamask2'
+
+const DEFAULT_NETWORK = 'rinkeby'
+const OLD_UI_NETWORK_TYPE = 'network'
+const BETA_UI_NETWORK_TYPE = 'networkBeta'
+
+module.exports = {
+ ROPSTEN,
+ RINKEBY,
+ KOVAN,
+ MAINNET,
+ LOCALHOST,
+ ROPSTEN_CODE,
+ RINKEYBY_CODE,
+ KOVAN_CODE,
+ ROPSTEN_DISPLAY_NAME,
+ RINKEBY_DISPLAY_NAME,
+ KOVAN_DISPLAY_NAME,
+ MAINNET_DISPLAY_NAME,
+ MAINNET_RPC_URL,
+ ROPSTEN_RPC_URL,
+ KOVAN_RPC_URL,
+ RINKEBY_RPC_URL,
+ LOCALHOST_RPC_URL,
+ MAINNET_RPC_URL_BETA,
+ ROPSTEN_RPC_URL_BETA,
+ KOVAN_RPC_URL_BETA,
+ RINKEBY_RPC_URL_BETA,
+ DEFAULT_NETWORK,
+ OLD_UI_NETWORK_TYPE,
+ BETA_UI_NETWORK_TYPE,
+}
diff --git a/app/scripts/controllers/network/index.js b/app/scripts/controllers/network/index.js
new file mode 100644
index 000000000..fb095bf33
--- /dev/null
+++ b/app/scripts/controllers/network/index.js
@@ -0,0 +1,2 @@
+const NetworkController = require('./network')
+module.exports = NetworkController
diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network/network.js
index 617456cd7..6fd983bb2 100644
--- a/app/scripts/controllers/network.js
+++ b/app/scripts/controllers/network/network.js
@@ -7,10 +7,18 @@ const ObservableStore = require('obs-store')
const ComposedStore = require('obs-store/lib/composed')
const extend = require('xtend')
const EthQuery = require('eth-query')
-const createEventEmitterProxy = require('../lib/events-proxy.js')
-const networkConfig = require('../config.js')
-const { OLD_UI_NETWORK_TYPE, DEFAULT_RPC } = networkConfig.enums
-const INFURA_PROVIDER_TYPES = ['ropsten', 'rinkeby', 'kovan', 'mainnet']
+const createEventEmitterProxy = require('../../lib/events-proxy.js')
+const log = require('loglevel')
+const {
+ ROPSTEN,
+ RINKEBY,
+ KOVAN,
+ MAINNET,
+ OLD_UI_NETWORK_TYPE,
+ DEFAULT_NETWORK,
+} = require('./enums')
+const { getNetworkEndpoints } = require('./util')
+const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET]
module.exports = class NetworkController extends EventEmitter {
@@ -18,8 +26,8 @@ module.exports = class NetworkController extends EventEmitter {
super()
this._networkEndpointVersion = OLD_UI_NETWORK_TYPE
- this._networkEndpoints = this.getNetworkEndpoints(OLD_UI_NETWORK_TYPE)
- this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
+ this._networkEndpoints = getNetworkEndpoints(OLD_UI_NETWORK_TYPE)
+ this._defaultRpc = this._networkEndpoints[DEFAULT_NETWORK]
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider)
this.networkStore = new ObservableStore('loading')
@@ -36,17 +44,13 @@ module.exports = class NetworkController extends EventEmitter {
}
this._networkEndpointVersion = version
- this._networkEndpoints = this.getNetworkEndpoints(version)
- this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
+ this._networkEndpoints = getNetworkEndpoints(version)
+ this._defaultRpc = this._networkEndpoints[DEFAULT_NETWORK]
const { type } = this.getProviderConfig()
return this.setProviderType(type, true)
}
- getNetworkEndpoints (version = OLD_UI_NETWORK_TYPE) {
- return networkConfig[version]
- }
-
initializeProvider (_providerParams) {
this._baseProviderParams = _providerParams
const { type, rpcTarget } = this.providerStore.getState()
diff --git a/app/scripts/controllers/network/util.js b/app/scripts/controllers/network/util.js
new file mode 100644
index 000000000..4f38ccda4
--- /dev/null
+++ b/app/scripts/controllers/network/util.js
@@ -0,0 +1,65 @@
+const {
+ ROPSTEN,
+ RINKEBY,
+ KOVAN,
+ MAINNET,
+ LOCALHOST,
+ ROPSTEN_CODE,
+ RINKEYBY_CODE,
+ KOVAN_CODE,
+ ROPSTEN_DISPLAY_NAME,
+ RINKEBY_DISPLAY_NAME,
+ KOVAN_DISPLAY_NAME,
+ MAINNET_DISPLAY_NAME,
+ MAINNET_RPC_URL,
+ ROPSTEN_RPC_URL,
+ KOVAN_RPC_URL,
+ RINKEBY_RPC_URL,
+ LOCALHOST_RPC_URL,
+ MAINNET_RPC_URL_BETA,
+ ROPSTEN_RPC_URL_BETA,
+ KOVAN_RPC_URL_BETA,
+ RINKEBY_RPC_URL_BETA,
+ OLD_UI_NETWORK_TYPE,
+ BETA_UI_NETWORK_TYPE,
+} = require('./enums')
+
+const networkToNameMap = {
+ [ROPSTEN]: ROPSTEN_DISPLAY_NAME,
+ [RINKEBY]: RINKEBY_DISPLAY_NAME,
+ [KOVAN]: KOVAN_DISPLAY_NAME,
+ [MAINNET]: MAINNET_DISPLAY_NAME,
+ [ROPSTEN_CODE]: ROPSTEN_DISPLAY_NAME,
+ [RINKEYBY_CODE]: RINKEBY_DISPLAY_NAME,
+ [KOVAN_CODE]: KOVAN_DISPLAY_NAME,
+}
+
+const networkEndpointsMap = {
+ [OLD_UI_NETWORK_TYPE]: {
+ [LOCALHOST]: LOCALHOST_RPC_URL,
+ [MAINNET]: MAINNET_RPC_URL,
+ [ROPSTEN]: ROPSTEN_RPC_URL,
+ [KOVAN]: KOVAN_RPC_URL,
+ [RINKEBY]: RINKEBY_RPC_URL,
+ },
+ [BETA_UI_NETWORK_TYPE]: {
+ [LOCALHOST]: LOCALHOST_RPC_URL,
+ [MAINNET]: MAINNET_RPC_URL_BETA,
+ [ROPSTEN]: ROPSTEN_RPC_URL_BETA,
+ [KOVAN]: KOVAN_RPC_URL_BETA,
+ [RINKEBY]: RINKEBY_RPC_URL_BETA,
+ },
+}
+
+const getNetworkDisplayName = key => networkToNameMap[key]
+
+const getNetworkEndpoints = (networkType = OLD_UI_NETWORK_TYPE) => {
+ return {
+ ...networkEndpointsMap[networkType],
+ }
+}
+
+module.exports = {
+ getNetworkDisplayName,
+ getNetworkEndpoints,
+}
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index b4819d951..d4d508026 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -4,6 +4,21 @@ const extend = require('xtend')
class PreferencesController {
+ /**
+ *
+ * @typedef {Object} PreferencesController
+ * @param {object} opts Overrides the defaults for the initial state of this.store
+ * @property {object} store The an object containing a users preferences, stored in local storage
+ * @property {array} store.frequentRpcList A list of custom rpcs to provide the user
+ * @property {string} store.currentAccountTab Indicates the selected tab in the ui
+ * @property {array} store.tokens The tokens the user wants display in their token lists
+ * @property {boolean} store.useBlockie The users preference for blockie identicons within the UI
+ * @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the
+ * user wishes to see that feature
+ * @property {string} store.currentLocale The preferred language locale key
+ * @property {string} store.selectedAddress A hex string that matches the currently selected address in the app
+ *
+ */
constructor (opts = {}) {
const initState = extend({
frequentRpcList: [],
@@ -17,18 +32,43 @@ class PreferencesController {
}
// PUBLIC METHODS
+ /**
+ * Setter for the `useBlockie` property
+ *
+ * @param {boolean} val Whether or not the user prefers blockie indicators
+ *
+ */
setUseBlockie (val) {
this.store.updateState({ useBlockie: val })
}
+ /**
+ * Getter for the `useBlockie` property
+ *
+ * @returns {boolean} this.store.useBlockie
+ *
+ */
getUseBlockie () {
return this.store.getState().useBlockie
}
+ /**
+ * Setter for the `currentLocale` property
+ *
+ * @param {string} key he preferred language locale key
+ *
+ */
setCurrentLocale (key) {
this.store.updateState({ currentLocale: key })
}
+ /**
+ * Setter for the `selectedAddress` property
+ *
+ * @param {string} _address A new hex address for an account
+ * @returns {Promise<void>} Promise resolves with undefined
+ *
+ */
setSelectedAddress (_address) {
return new Promise((resolve, reject) => {
const address = normalizeAddress(_address)
@@ -37,10 +77,37 @@ class PreferencesController {
})
}
+ /**
+ * Getter for the `selectedAddress` property
+ *
+ * @returns {string} The hex address for the currently selected account
+ *
+ */
getSelectedAddress () {
return this.store.getState().selectedAddress
}
+ /**
+ * Contains data about tokens users add to their account.
+ * @typedef {Object} AddedToken
+ * @property {string} address - The hex address for the token contract. Will be all lower cased and hex-prefixed.
+ * @property {string} symbol - The symbol of the token, usually 3 or 4 capitalized letters
+ * {@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#symbol}
+ * @property {boolean} decimals - The number of decimals the token uses.
+ * {@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#decimals}
+ */
+
+ /**
+ * Adds a new token to the token array, or updates the token if passed an address that already exists.
+ * Modifies the existing tokens array from the store. All objects in the tokens array array AddedToken objects.
+ * @see AddedToken {@link AddedToken}
+ *
+ * @param {string} rawAddress Hex address of the token contract. May or may not be a checksum address.
+ * @param {string} symbol The symbol of the token
+ * @param {number} decimals The number of decimals the token uses.
+ * @returns {Promise<array>} Promises the new array of AddedToken objects.
+ *
+ */
async addToken (rawAddress, symbol, decimals) {
const address = normalizeAddress(rawAddress)
const newEntry = { address, symbol, decimals }
@@ -62,6 +129,13 @@ class PreferencesController {
return Promise.resolve(tokens)
}
+ /**
+ * Removes a specified token from the tokens array.
+ *
+ * @param {string} rawAddress Hex address of the token contract to remove.
+ * @returns {Promise<array>} The new array of AddedToken objects
+ *
+ */
removeToken (rawAddress) {
const tokens = this.store.getState().tokens
@@ -71,10 +145,23 @@ class PreferencesController {
return Promise.resolve(updatedTokens)
}
+ /**
+ * A getter for the `tokens` property
+ *
+ * @returns {array} The current array of AddedToken objects
+ *
+ */
getTokens () {
return this.store.getState().tokens
}
+ /**
+ * Gets an updated rpc list from this.addToFrequentRpcList() and sets the `frequentRpcList` to this update list.
+ *
+ * @param {string} _url The the new rpc url to add to the updated list
+ * @returns {Promise<void>} Promise resolves with undefined
+ *
+ */
updateFrequentRpcList (_url) {
return this.addToFrequentRpcList(_url)
.then((rpcList) => {
@@ -83,6 +170,13 @@ class PreferencesController {
})
}
+ /**
+ * Setter for the `currentAccountTab` property
+ *
+ * @param {string} currentAccountTab Specifies the new tab to be marked as current
+ * @returns {Promise<void>} Promise resolves with undefined
+ *
+ */
setCurrentAccountTab (currentAccountTab) {
return new Promise((resolve, reject) => {
this.store.updateState({ currentAccountTab })
@@ -90,6 +184,15 @@ class PreferencesController {
})
}
+ /**
+ * Returns an updated rpcList based on the passed url and the current list.
+ * The returned list will have a max length of 2. If the _url currently exists it the list, it will be moved to the
+ * end of the list. The current list is modified and returned as a promise.
+ *
+ * @param {string} _url The rpc url to add to the frequentRpcList.
+ * @returns {Promise<array>} The updated frequentRpcList.
+ *
+ */
addToFrequentRpcList (_url) {
const rpcList = this.getFrequentRpcList()
const index = rpcList.findIndex((element) => { return element === _url })
@@ -105,10 +208,24 @@ class PreferencesController {
return Promise.resolve(rpcList)
}
+ /**
+ * Getter for the `frequentRpcList` property.
+ *
+ * @returns {array<string>} An array of one or two rpc urls.
+ *
+ */
getFrequentRpcList () {
return this.store.getState().frequentRpcList
}
+ /**
+ * Updates the `featureFlags` property, which is an object. One property within that object will be set to a boolean.
+ *
+ * @param {string} feature A key that corresponds to a UI feature.
+ * @param {boolean} activated Indicates whether or not the UI feature should be displayed
+ * @returns {Promise<object>} Promises a new object; the updated featureFlags object.
+ *
+ */
setFeatureFlag (feature, activated) {
const currentFeatureFlags = this.store.getState().featureFlags
const updatedFeatureFlags = {
@@ -121,6 +238,13 @@ class PreferencesController {
return Promise.resolve(updatedFeatureFlags)
}
+ /**
+ * A getter for the `featureFlags` property
+ *
+ * @returns {object} A key-boolean map, where keys refer to features and booleans to whether the
+ * user wishes to see that feature
+ *
+ */
getFeatureFlags () {
return this.store.getState().featureFlags
}
diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js
index 4ae3810eb..0c1ee4e38 100644
--- a/app/scripts/controllers/recent-blocks.js
+++ b/app/scripts/controllers/recent-blocks.js
@@ -2,6 +2,7 @@ const ObservableStore = require('obs-store')
const extend = require('xtend')
const BN = require('ethereumjs-util').BN
const EthQuery = require('eth-query')
+const log = require('loglevel')
class RecentBlocksController {
diff --git a/app/scripts/controllers/shapeshift.js b/app/scripts/controllers/shapeshift.js
index 3bbfaa1c5..b2a1462c2 100644
--- a/app/scripts/controllers/shapeshift.js
+++ b/app/scripts/controllers/shapeshift.js
@@ -1,11 +1,23 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
+const log = require('loglevel')
// every three seconds when an incomplete tx is waiting
const POLLING_INTERVAL = 3000
class ShapeshiftController {
+ /**
+ * Controller responsible for managing the list of shapeshift transactions. On construction, it initiates a poll
+ * that queries a shapeshift.io API for updates to any pending shapeshift transactions
+ *
+ * @typedef {Object} ShapeshiftController
+ * @param {object} opts Overrides the defaults for the initial state of this.store
+ * @property {array} opts.initState initializes the the state of the ShapeshiftController. Can contain an
+ * shapeShiftTxList array.
+ * @property {array} shapeShiftTxList An array of ShapeShiftTx objects
+ *
+ */
constructor (opts = {}) {
const initState = extend({
shapeShiftTxList: [],
@@ -14,21 +26,54 @@ class ShapeshiftController {
this.pollForUpdates()
}
+ /**
+ * Represents, and contains data about, a single shapeshift transaction.
+ * @typedef {Object} ShapeShiftTx
+ * @property {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the
+ * user's Metamask account
+ * @property {string} depositType - An abbreviation of the type of crypto currency to be deposited.
+ * @property {string} key - The 'shapeshift' key differentiates this from other types of txs in Metamask
+ * @property {number} time - The time at which the tx was created
+ * @property {object} response - Initiated as an empty object, which will be replaced by a Response object. @see {@link
+ * https://developer.mozilla.org/en-US/docs/Web/API/Response}
+ */
+
//
// PUBLIC METHODS
//
+ /**
+ * A getter for the shapeShiftTxList property
+ *
+ * @returns {array<ShapeShiftTx>}
+ *
+ */
getShapeShiftTxList () {
const shapeShiftTxList = this.store.getState().shapeShiftTxList
return shapeShiftTxList
}
+ /**
+ * A getter for all ShapeShiftTx in the shapeShiftTxList that have not successfully completed a deposit.
+ *
+ * @returns {array<ShapeShiftTx>} Only includes ShapeShiftTx which has a response property with a status !== complete
+ *
+ */
getPendingTxs () {
const txs = this.getShapeShiftTxList()
const pending = txs.filter(tx => tx.response && tx.response.status !== 'complete')
return pending
}
+ /**
+ * A poll that exists as long as there are pending transactions. Each call attempts to update the data of any
+ * pendingTxs, and then calls itself again. If there are no pending txs, the recursive call is not made and
+ * the polling stops.
+ *
+ * this.updateTx is used to attempt the update to the pendingTxs in the ShapeShiftTxList, and that updated data
+ * is saved with saveTx.
+ *
+ */
pollForUpdates () {
const pendingTxs = this.getPendingTxs()
@@ -45,6 +90,15 @@ class ShapeshiftController {
})
}
+ /**
+ * Attempts to update a ShapeShiftTx with data from a shapeshift.io API. Both the response and time properties
+ * can be updated. The response property is updated with every call, but the time property is only updated when
+ * the response status updates to 'complete'. This will occur once the user makes a deposit as the ShapeShiftTx
+ * depositAddress
+ *
+ * @param {ShapeShiftTx} tx The tx to update
+ *
+ */
async updateTx (tx) {
try {
const url = `https://shapeshift.io/txStat/${tx.depositAddress}`
@@ -60,6 +114,13 @@ class ShapeshiftController {
}
}
+ /**
+ * Saves an updated to a ShapeShiftTx in the shapeShiftTxList. If the passed ShapeShiftTx is not in the
+ * shapeShiftTxList, nothing happens.
+ *
+ * @param {ShapeShiftTx} tx The updated tx to save, if it exists in the current shapeShiftTxList
+ *
+ */
saveTx (tx) {
const { shapeShiftTxList } = this.store.getState()
const index = shapeShiftTxList.indexOf(tx)
@@ -69,6 +130,12 @@ class ShapeshiftController {
}
}
+ /**
+ * Removes a ShapeShiftTx from the shapeShiftTxList
+ *
+ * @param {ShapeShiftTx} tx The tx to remove
+ *
+ */
removeShapeShiftTx (tx) {
const { shapeShiftTxList } = this.store.getState()
const index = shapeShiftTxList.indexOf(index)
@@ -78,6 +145,14 @@ class ShapeshiftController {
this.updateState({ shapeShiftTxList })
}
+ /**
+ * Creates a new ShapeShiftTx, adds it to the shapeShiftTxList, and initiates a new poll for updates of pending txs
+ *
+ * @param {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the
+ * user's Metamask account
+ * @param {string} depositType - An abbreviation of the type of crypto currency to be deposited.
+ *
+ */
createShapeShiftTx (depositAddress, depositType) {
const state = this.store.getState()
let { shapeShiftTxList } = state
diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js
new file mode 100644
index 000000000..21384f262
--- /dev/null
+++ b/app/scripts/controllers/token-rates.js
@@ -0,0 +1,77 @@
+const ObservableStore = require('obs-store')
+
+// By default, poll every 3 minutes
+const DEFAULT_INTERVAL = 180 * 1000
+
+/**
+ * A controller that polls for token exchange
+ * rates based on a user's current token list
+ */
+class TokenRatesController {
+ /**
+ * Creates a TokenRatesController
+ *
+ * @param {Object} [config] - Options to configure controller
+ */
+ constructor ({ interval = DEFAULT_INTERVAL, preferences } = {}) {
+ this.store = new ObservableStore()
+ this.preferences = preferences
+ this.interval = interval
+ }
+
+ /**
+ * Updates exchange rates for all tokens
+ */
+ async updateExchangeRates () {
+ if (!this.isActive) { return }
+ const contractExchangeRates = {}
+ for (const i in this._tokens) {
+ const address = this._tokens[i].address
+ contractExchangeRates[address] = await this.fetchExchangeRate(address)
+ }
+ 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.dev.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) { }
+ }
+
+ /**
+ * @type {Number}
+ */
+ set interval (interval) {
+ this._handle && clearInterval(this._handle)
+ if (!interval) { return }
+ this._handle = setInterval(() => { this.updateExchangeRates() }, interval)
+ }
+
+ /**
+ * @type {Object}
+ */
+ set preferences (preferences) {
+ this._preferences && this._preferences.unsubscribe()
+ if (!preferences) { return }
+ this._preferences = preferences
+ this.tokens = preferences.getState().tokens
+ preferences.subscribe(({ tokens = [] }) => { this.tokens = tokens })
+ }
+
+ /**
+ * @type {Array}
+ */
+ set tokens (tokens) {
+ this._tokens = tokens
+ this.updateExchangeRates()
+ }
+}
+
+module.exports = TokenRatesController
diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js
index 336b0d8f7..c8211ebd7 100644
--- a/app/scripts/controllers/transactions.js
+++ b/app/scripts/controllers/transactions.js
@@ -7,6 +7,7 @@ const TransactionStateManager = require('../lib/tx-state-manager')
const TxGasUtil = require('../lib/tx-gas-utils')
const PendingTransactionTracker = require('../lib/pending-tx-tracker')
const NonceTracker = require('../lib/nonce-tracker')
+const log = require('loglevel')
/*
Transaction Controller is an aggregate of sub-controllers and trackers
diff --git a/app/scripts/edge-encryptor.js b/app/scripts/edge-encryptor.js
index 24c0c93a8..dcb06873b 100644
--- a/app/scripts/edge-encryptor.js
+++ b/app/scripts/edge-encryptor.js
@@ -1,69 +1,97 @@
const asmcrypto = require('asmcrypto.js')
const Unibabel = require('browserify-unibabel')
+/**
+ * A Microsoft Edge-specific encryption class that exposes
+ * the interface expected by eth-keykeyring-controller
+ */
class EdgeEncryptor {
+ /**
+ * Encrypts an arbitrary object to ciphertext
+ *
+ * @param {string} password Used to generate a key to encrypt the data
+ * @param {Object} dataObject Data to encrypt
+ * @returns {Promise<string>} Promise resolving to an object with ciphertext
+ */
+ encrypt (password, dataObject) {
+ var salt = this._generateSalt()
+ return this._keyFromPassword(password, salt)
+ .then(function (key) {
+ var data = JSON.stringify(dataObject)
+ var dataBuffer = Unibabel.utf8ToBuffer(data)
+ var vector = global.crypto.getRandomValues(new Uint8Array(16))
+ var resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector)
- encrypt (password, dataObject) {
+ var buffer = new Uint8Array(resultbuffer)
+ var vectorStr = Unibabel.bufferToBase64(vector)
+ var vaultStr = Unibabel.bufferToBase64(buffer)
+ return JSON.stringify({
+ data: vaultStr,
+ iv: vectorStr,
+ salt: salt,
+ })
+ })
+ }
- var salt = this._generateSalt()
- return this._keyFromPassword(password, salt)
- .then(function (key) {
+ /**
+ * Decrypts an arbitrary object from ciphertext
+ *
+ * @param {string} password Used to generate a key to decrypt the data
+ * @param {string} text Ciphertext of an encrypted object
+ * @returns {Promise<Object>} Promise resolving to copy of decrypted object
+ */
+ decrypt (password, text) {
+ const payload = JSON.parse(text)
+ const salt = payload.salt
+ return this._keyFromPassword(password, salt)
+ .then(function (key) {
+ const encryptedData = Unibabel.base64ToBuffer(payload.data)
+ const vector = Unibabel.base64ToBuffer(payload.iv)
+ return new Promise((resolve, reject) => {
+ var result
+ try {
+ result = asmcrypto.AES_GCM.decrypt(encryptedData, key, vector)
+ } catch (err) {
+ return reject(new Error('Incorrect password'))
+ }
+ const decryptedData = new Uint8Array(result)
+ const decryptedStr = Unibabel.bufferToUtf8(decryptedData)
+ const decryptedObj = JSON.parse(decryptedStr)
+ resolve(decryptedObj)
+ })
+ })
+ }
- var data = JSON.stringify(dataObject)
- var dataBuffer = Unibabel.utf8ToBuffer(data)
- var vector = global.crypto.getRandomValues(new Uint8Array(16))
- var resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector)
+ /**
+ * Retrieves a cryptographic key using a password
+ *
+ * @private
+ * @param {string} password Password used to unlock a cryptographic key
+ * @param {string} salt Random base64 data
+ * @returns {Promise<Object>} Promise resolving to a derived key
+ */
+ _keyFromPassword (password, salt) {
- var buffer = new Uint8Array(resultbuffer)
- var vectorStr = Unibabel.bufferToBase64(vector)
- var vaultStr = Unibabel.bufferToBase64(buffer)
- return JSON.stringify({
- data: vaultStr,
- iv: vectorStr,
- salt: salt,
- })
- })
- }
+ var passBuffer = Unibabel.utf8ToBuffer(password)
+ var saltBuffer = Unibabel.base64ToBuffer(salt)
+ return new Promise((resolve) => {
+ var key = asmcrypto.PBKDF2_HMAC_SHA256.bytes(passBuffer, saltBuffer, 10000)
+ resolve(key)
+ })
+ }
- decrypt (password, text) {
-
- const payload = JSON.parse(text)
- const salt = payload.salt
- return this._keyFromPassword(password, salt)
- .then(function (key) {
- const encryptedData = Unibabel.base64ToBuffer(payload.data)
- const vector = Unibabel.base64ToBuffer(payload.iv)
- return new Promise((resolve, reject) => {
- var result
- try {
- result = asmcrypto.AES_GCM.decrypt(encryptedData, key, vector)
- } catch (err) {
- return reject(new Error('Incorrect password'))
- }
- const decryptedData = new Uint8Array(result)
- const decryptedStr = Unibabel.bufferToUtf8(decryptedData)
- const decryptedObj = JSON.parse(decryptedStr)
- resolve(decryptedObj)
- })
- })
- }
-
- _keyFromPassword (password, salt) {
-
- var passBuffer = Unibabel.utf8ToBuffer(password)
- var saltBuffer = Unibabel.base64ToBuffer(salt)
- return new Promise((resolve) => {
- var key = asmcrypto.PBKDF2_HMAC_SHA256.bytes(passBuffer, saltBuffer, 10000)
- resolve(key)
- })
- }
-
- _generateSalt (byteCount = 32) {
- var view = new Uint8Array(byteCount)
- global.crypto.getRandomValues(view)
- var b64encoded = btoa(String.fromCharCode.apply(null, view))
- return b64encoded
- }
+ /**
+ * Generates random base64 encoded data
+ *
+ * @private
+ * @returns {string} Randomized base64 encoded data
+ */
+ _generateSalt (byteCount = 32) {
+ var view = new Uint8Array(byteCount)
+ global.crypto.getRandomValues(view)
+ var b64encoded = btoa(String.fromCharCode.apply(null, view))
+ return b64encoded
+ }
}
module.exports = EdgeEncryptor
diff --git a/app/scripts/first-time-state.js b/app/scripts/first-time-state.js
index 3063df627..c49d89288 100644
--- a/app/scripts/first-time-state.js
+++ b/app/scripts/first-time-state.js
@@ -1,15 +1,24 @@
// test and development environment variables
const env = process.env.METAMASK_ENV
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
+const { DEFAULT_NETWORK, MAINNET } = require('./controllers/network/enums')
-//
-// The default state of MetaMask
-//
-module.exports = {
+/**
+ * @typedef {Object} FirstTimeState
+ * @property {Object} config Initial configuration parameters
+ * @property {Object} NetworkController Network controller state
+ */
+
+/**
+ * @type {FirstTimeState}
+ */
+const initialState = {
config: {},
NetworkController: {
provider: {
- type: (METAMASK_DEBUG || env === 'test') ? 'rinkeby' : 'mainnet',
+ type: (METAMASK_DEBUG || env === 'test') ? DEFAULT_NETWORK : MAINNET,
},
},
}
+
+module.exports = initialState
diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js
index ec99bfc35..6d16eebd4 100644
--- a/app/scripts/inpage.js
+++ b/app/scripts/inpage.js
@@ -3,16 +3,11 @@ cleanContextForImports()
require('web3/dist/web3.min.js')
const log = require('loglevel')
const LocalMessageDuplexStream = require('post-message-stream')
-// const PingStream = require('ping-pong-stream/ping')
-// const endOfStream = require('end-of-stream')
const setupDappAutoReload = require('./lib/auto-reload.js')
const MetamaskInpageProvider = require('./lib/inpage-provider.js')
restoreContextAfterImports()
-const METAMASK_DEBUG = process.env.METAMASK_DEBUG
-window.log = log
-log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
-
+log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
//
// setup plugin communication
@@ -47,20 +42,20 @@ log.debug('MetaMask - injected web3')
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
// set web3 defaultAccount
-
inpageProvider.publicConfigStore.subscribe(function (state) {
web3.eth.defaultAccount = state.selectedAddress
})
-//
-// util
-//
-
// need to make sure we aren't affected by overlapping namespaces
// and that we dont affect the app with our namespace
// mostly a fix for web3's BigNumber if AMD's "define" is defined...
var __define
+/**
+ * Caches reference to global define object and deletes it to
+ * avoid conflicts with other global define objects, such as
+ * AMD's define function
+ */
function cleanContextForImports () {
__define = global.define
try {
@@ -70,6 +65,9 @@ function cleanContextForImports () {
}
}
+/**
+ * Restores global define object from cached reference
+ */
function restoreContextAfterImports () {
try {
global.define = __define
diff --git a/app/scripts/lib/ComposableObservableStore.js b/app/scripts/lib/ComposableObservableStore.js
new file mode 100644
index 000000000..d5ee708a1
--- /dev/null
+++ b/app/scripts/lib/ComposableObservableStore.js
@@ -0,0 +1,49 @@
+const ObservableStore = require('obs-store')
+
+/**
+ * An ObservableStore that can composes a flat
+ * structure of child stores based on configuration
+ */
+class ComposableObservableStore extends ObservableStore {
+ /**
+ * Create a new store
+ *
+ * @param {Object} [initState] - The initial store state
+ * @param {Object} [config] - Map of internal state keys to child stores
+ */
+ constructor (initState, config) {
+ super(initState)
+ this.updateStructure(config)
+ }
+
+ /**
+ * Composes a new internal store subscription structure
+ *
+ * @param {Object} [config] - Map of internal state keys to child stores
+ */
+ updateStructure (config) {
+ this.config = config
+ this.removeAllListeners()
+ for (const key in config) {
+ config[key].subscribe((state) => {
+ this.updateState({ [key]: state })
+ })
+ }
+ }
+
+ /**
+ * Merges all child store state into a single object rather than
+ * returning an object keyed by child store class name
+ *
+ * @returns {Object} - Object containing merged child store state
+ */
+ getFlatState () {
+ let flatState = {}
+ for (const key in this.config) {
+ flatState = { ...flatState, ...this.config[key].getState() }
+ }
+ return flatState
+ }
+}
+
+module.exports = ComposableObservableStore
diff --git a/app/scripts/lib/buy-eth-url.js b/app/scripts/lib/buy-eth-url.js
index b9dde3c28..4e2d0bc79 100644
--- a/app/scripts/lib/buy-eth-url.js
+++ b/app/scripts/lib/buy-eth-url.js
@@ -1,5 +1,16 @@
module.exports = getBuyEthUrl
+/**
+ * Gives the caller a url at which the user can acquire eth, depending on the network they are in
+ *
+ * @param {object} opts Options required to determine the correct url
+ * @param {string} opts.network The network for which to return a url
+ * @param {string} opts.amount The amount of ETH to buy on coinbase. Only relevant if network === '1'.
+ * @param {string} opts.address The address the bought ETH should be sent to. Only relevant if network === '1'.
+ * @returns {string|undefined} The url at which the user can access ETH, while in the given network. If the passed
+ * network does not match any of the specified cases, or if no network is given, returns undefined.
+ *
+ */
function getBuyEthUrl ({ network, amount, address }) {
let url
switch (network) {
diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js
index 34b603b96..c10ff2f4e 100644
--- a/app/scripts/lib/config-manager.js
+++ b/app/scripts/lib/config-manager.js
@@ -1,12 +1,11 @@
const ethUtil = require('ethereumjs-util')
const normalize = require('eth-sig-util').normalize
-const MetamaskConfig = require('../config.js')
-
-
-const MAINNET_RPC = MetamaskConfig.network.mainnet
-const ROPSTEN_RPC = MetamaskConfig.network.ropsten
-const KOVAN_RPC = MetamaskConfig.network.kovan
-const RINKEBY_RPC = MetamaskConfig.network.rinkeby
+const {
+ MAINNET_RPC_URL,
+ ROPSTEN_RPC_URL,
+ KOVAN_RPC_URL,
+ RINKEBY_RPC_URL,
+} = require('../controllers/network/enums')
/* The config-manager is a convenience object
* wrapping a pojo-migrator.
@@ -102,7 +101,6 @@ ConfigManager.prototype.setShowSeedWords = function (should) {
this.setData(data)
}
-
ConfigManager.prototype.getShouldShowSeedWords = function () {
var data = this.getData()
return data.showSeedWords
@@ -118,6 +116,27 @@ ConfigManager.prototype.getSeedWords = function () {
var data = this.getData()
return data.seedWords
}
+
+/**
+ * Called to set the isRevealingSeedWords flag. This happens only when the user chooses to reveal
+ * the seed words and not during the first time flow.
+ * @param {boolean} reveal - Value to set the isRevealingSeedWords flag.
+ */
+ConfigManager.prototype.setIsRevealingSeedWords = function (reveal = false) {
+ const data = this.getData()
+ data.isRevealingSeedWords = reveal
+ this.setData(data)
+}
+
+/**
+ * Returns the isRevealingSeedWords flag.
+ * @returns {boolean|undefined}
+ */
+ConfigManager.prototype.getIsRevealingSeedWords = function () {
+ const data = this.getData()
+ return data.isRevealingSeedWords
+}
+
ConfigManager.prototype.setRpcTarget = function (rpcUrl) {
var config = this.getConfig()
config.provider = {
@@ -154,19 +173,19 @@ ConfigManager.prototype.getCurrentRpcAddress = function () {
switch (provider.type) {
case 'mainnet':
- return MAINNET_RPC
+ return MAINNET_RPC_URL
case 'ropsten':
- return ROPSTEN_RPC
+ return ROPSTEN_RPC_URL
case 'kovan':
- return KOVAN_RPC
+ return KOVAN_RPC_URL
case 'rinkeby':
- return RINKEBY_RPC
+ return RINKEBY_RPC_URL
default:
- return provider && provider.rpcTarget ? provider.rpcTarget : RINKEBY_RPC
+ return provider && provider.rpcTarget ? provider.rpcTarget : RINKEBY_RPC_URL
}
}
diff --git a/app/scripts/lib/createLoggerMiddleware.js b/app/scripts/lib/createLoggerMiddleware.js
index 2707cbd9e..996c3477c 100644
--- a/app/scripts/lib/createLoggerMiddleware.js
+++ b/app/scripts/lib/createLoggerMiddleware.js
@@ -1,14 +1,20 @@
-// log rpc activity
+const log = require('loglevel')
+
module.exports = createLoggerMiddleware
-function createLoggerMiddleware ({ origin }) {
- return function loggerMiddleware (req, res, next, end) {
- next((cb) => {
+/**
+ * Returns a middleware that logs RPC activity
+ * @param {{ origin: string }} opts - The middleware options
+ * @returns {Function}
+ */
+function createLoggerMiddleware (opts) {
+ return function loggerMiddleware (/** @type {any} */ req, /** @type {any} */ res, /** @type {Function} */ next) {
+ next((/** @type {Function} */ cb) => {
if (res.error) {
log.error('Error in RPC response:\n', res)
}
if (req.isMetamaskInternal) return
- log.info(`RPC (${origin}):`, req, '->', res)
+ log.info(`RPC (${opts.origin}):`, req, '->', res)
cb()
})
}
diff --git a/app/scripts/lib/createOriginMiddleware.js b/app/scripts/lib/createOriginMiddleware.js
index f8bdb2dc2..98bb0e3b3 100644
--- a/app/scripts/lib/createOriginMiddleware.js
+++ b/app/scripts/lib/createOriginMiddleware.js
@@ -1,9 +1,13 @@
-// append dapp origin domain to request
module.exports = createOriginMiddleware
-function createOriginMiddleware ({ origin }) {
- return function originMiddleware (req, res, next, end) {
- req.origin = origin
+/**
+ * Returns a middleware that appends the DApp origin to request
+ * @param {{ origin: string }} opts - The middleware options
+ * @returns {Function}
+ */
+function createOriginMiddleware (opts) {
+ return function originMiddleware (/** @type {any} */ req, /** @type {any} */ _, /** @type {Function} */ next) {
+ req.origin = opts.origin
next()
}
}
diff --git a/app/scripts/lib/createProviderMiddleware.js b/app/scripts/lib/createProviderMiddleware.js
index 4e667bac2..8a939ba4e 100644
--- a/app/scripts/lib/createProviderMiddleware.js
+++ b/app/scripts/lib/createProviderMiddleware.js
@@ -1,6 +1,10 @@
module.exports = createProviderMiddleware
-// forward requests to provider
+/**
+ * Forwards an HTTP request to the current Web3 provider
+ *
+ * @param {{ provider: Object }} config Configuration containing current Web3 provider
+ */
function createProviderMiddleware ({ provider }) {
return (req, res, next, end) => {
provider.sendAsync(req, (err, _res) => {
diff --git a/app/scripts/lib/enums.js b/app/scripts/lib/enums.js
new file mode 100644
index 000000000..0a3afca47
--- /dev/null
+++ b/app/scripts/lib/enums.js
@@ -0,0 +1,9 @@
+const ENVIRONMENT_TYPE_POPUP = 'popup'
+const ENVIRONMENT_TYPE_NOTIFICATION = 'notification'
+const ENVIRONMENT_TYPE_FULLSCREEN = 'fullscreen'
+
+module.exports = {
+ ENVIRONMENT_TYPE_POPUP,
+ ENVIRONMENT_TYPE_NOTIFICATION,
+ ENVIRONMENT_TYPE_FULLSCREEN,
+}
diff --git a/app/scripts/lib/environment-type.js b/app/scripts/lib/environment-type.js
deleted file mode 100644
index 7966926eb..000000000
--- a/app/scripts/lib/environment-type.js
+++ /dev/null
@@ -1,10 +0,0 @@
-module.exports = function environmentType () {
- const url = window.location.href
- if (url.match(/popup.html$/)) {
- return 'popup'
- } else if (url.match(/home.html$/)) {
- return 'responsive'
- } else {
- return 'notification'
- }
-}
diff --git a/app/scripts/lib/events-proxy.js b/app/scripts/lib/events-proxy.js
index c0a490b05..f83773ccc 100644
--- a/app/scripts/lib/events-proxy.js
+++ b/app/scripts/lib/events-proxy.js
@@ -1,26 +1,37 @@
+/**
+ * Returns an EventEmitter that proxies events from the given event emitter
+ * @param {any} eventEmitter
+ * @param {object} listeners - The listeners to proxy to
+ * @returns {any}
+ */
module.exports = function createEventEmitterProxy (eventEmitter, listeners) {
let target = eventEmitter
const eventHandlers = listeners || {}
- const proxy = new Proxy({}, {
- get: (obj, name) => {
+ const proxy = /** @type {any} */ (new Proxy({}, {
+ get: (_, name) => {
// intercept listeners
if (name === 'on') return addListener
if (name === 'setTarget') return setTarget
if (name === 'proxyEventHandlers') return eventHandlers
- return target[name]
+ return (/** @type {any} */ (target))[name]
},
- set: (obj, name, value) => {
+ set: (_, name, value) => {
target[name] = value
return true
},
- })
- function setTarget (eventEmitter) {
+ }))
+ function setTarget (/** @type {EventEmitter} */ eventEmitter) {
target = eventEmitter
// migrate listeners
Object.keys(eventHandlers).forEach((name) => {
- eventHandlers[name].forEach((handler) => target.on(name, handler))
+ /** @type {Array<Function>} */ (eventHandlers[name]).forEach((handler) => target.on(name, handler))
})
}
+ /**
+ * Attaches a function to be called whenever the specified event is emitted
+ * @param {string} name
+ * @param {Function} handler
+ */
function addListener (name, handler) {
if (!eventHandlers[name]) eventHandlers[name] = []
eventHandlers[name].push(handler)
diff --git a/app/scripts/lib/get-first-preferred-lang-code.js b/app/scripts/lib/get-first-preferred-lang-code.js
index e3635434e..5473fccf0 100644
--- a/app/scripts/lib/get-first-preferred-lang-code.js
+++ b/app/scripts/lib/get-first-preferred-lang-code.js
@@ -4,6 +4,13 @@ const allLocales = require('../../_locales/index.json')
const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().replace('_', '-'))
+/**
+ * Returns a preferred language code, based on settings within the user's browser. If we have no translations for the
+ * users preferred locales, 'en' is returned.
+ *
+ * @returns {Promise<string>} Promises a locale code, either one from the user's preferred list that we have a translation for, or 'en'
+ *
+ */
async function getFirstPreferredLangCode () {
const userPreferredLocaleCodes = await promisify(
extension.i18n.getAcceptLanguages,
diff --git a/app/scripts/lib/hex-to-bn.js b/app/scripts/lib/hex-to-bn.js
index 184217279..b28746920 100644
--- a/app/scripts/lib/hex-to-bn.js
+++ b/app/scripts/lib/hex-to-bn.js
@@ -1,6 +1,11 @@
-const ethUtil = require('ethereumjs-util')
+const ethUtil = (/** @type {object} */ (require('ethereumjs-util')))
const BN = ethUtil.BN
+/**
+ * Returns a [BinaryNumber]{@link BN} representation of the given hex value
+ * @param {string} hex
+ * @return {any}
+ */
module.exports = function hexToBn (hex) {
return new BN(ethUtil.stripHexPrefix(hex), 16)
}
diff --git a/app/scripts/lib/is-popup-or-notification.js b/app/scripts/lib/is-popup-or-notification.js
deleted file mode 100644
index e2999411f..000000000
--- a/app/scripts/lib/is-popup-or-notification.js
+++ /dev/null
@@ -1,11 +0,0 @@
-module.exports = function isPopupOrNotification () {
- const url = window.location.href
- // if (url.match(/popup.html$/) || url.match(/home.html$/)) {
- // Below regexes needed for feature toggles (e.g. see line ~340 in ui/app/app.js)
- // Revert below regexes to above commented out regexes before merge to master
- if (url.match(/popup.html(?:\?.+)*$/) || url.match(/home.html(?:\?.+)*$/)) {
- return 'popup'
- } else {
- return 'notification'
- }
-}
diff --git a/app/scripts/lib/local-store.js b/app/scripts/lib/local-store.js
index 5b47985f6..139ff86bd 100644
--- a/app/scripts/lib/local-store.js
+++ b/app/scripts/lib/local-store.js
@@ -1,10 +1,13 @@
-// We should not rely on local storage in an extension!
-// We should use this instead!
-// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/storage/local
-
const extension = require('extensionizer')
+const log = require('loglevel')
+/**
+ * A wrapper around the extension's storage local API
+ */
module.exports = class ExtensionStore {
+ /**
+ * @constructor
+ */
constructor() {
this.isSupported = !!(extension.storage.local)
if (!this.isSupported) {
@@ -12,6 +15,10 @@ module.exports = class ExtensionStore {
}
}
+ /**
+ * Returns all of the keys currently saved
+ * @return {Promise<*>}
+ */
async get() {
if (!this.isSupported) return undefined
const result = await this._get()
@@ -24,14 +31,24 @@ module.exports = class ExtensionStore {
}
}
+ /**
+ * Sets the key in local state
+ * @param {object} state - The state to set
+ * @return {Promise<void>}
+ */
async set(state) {
return this._set(state)
}
+ /**
+ * Returns all of the keys currently saved
+ * @private
+ * @return {object} the key-value map from local storage
+ */
_get() {
const local = extension.storage.local
return new Promise((resolve, reject) => {
- local.get(null, (result) => {
+ local.get(null, (/** @type {any} */ result) => {
const err = extension.runtime.lastError
if (err) {
reject(err)
@@ -42,6 +59,12 @@ module.exports = class ExtensionStore {
})
}
+ /**
+ * Sets the key in local state
+ * @param {object} obj - The key to set
+ * @return {Promise<void>}
+ * @private
+ */
_set(obj) {
const local = extension.storage.local
return new Promise((resolve, reject) => {
@@ -57,6 +80,11 @@ module.exports = class ExtensionStore {
}
}
+/**
+ * Returns whether or not the given object contains no keys
+ * @param {object} obj - The object to check
+ * @returns {boolean}
+ */
function isEmpty(obj) {
return Object.keys(obj).length === 0
}
diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js
index 85c2717ea..345ca8001 100644
--- a/app/scripts/lib/migrator/index.js
+++ b/app/scripts/lib/migrator/index.js
@@ -1,7 +1,23 @@
const EventEmitter = require('events')
+/**
+ * @typedef {object} Migration
+ * @property {number} version - The migration version
+ * @property {Function} migrate - Returns a promise of the migrated data
+ */
+
+/**
+ * @typedef {object} MigratorOptions
+ * @property {Array<Migration>} [migrations] - The list of migrations to apply
+ * @property {number} [defaultVersion] - The version to use in the initial state
+ */
+
class Migrator extends EventEmitter {
+ /**
+ * @constructor
+ * @param {MigratorOptions} opts
+ */
constructor (opts = {}) {
super()
const migrations = opts.migrations || []
@@ -42,19 +58,30 @@ class Migrator extends EventEmitter {
return versionedData
- // migration is "pending" if it has a higher
- // version number than currentVersion
+ /**
+ * Returns whether or not the migration is pending
+ *
+ * A migration is considered "pending" if it has a higher
+ * version number than the current version.
+ * @param {Migration} migration
+ * @returns {boolean}
+ */
function migrationIsPending (migration) {
return migration.version > versionedData.meta.version
}
}
- generateInitialState (initState) {
+ /**
+ * Returns the initial state for the migrator
+ * @param {object} [data] - The data for the initial state
+ * @returns {{meta: {version: number}, data: any}}
+ */
+ generateInitialState (data) {
return {
meta: {
version: this.defaultVersion,
},
- data: initState,
+ data,
}
}
diff --git a/app/scripts/lib/nodeify.js b/app/scripts/lib/nodeify.js
index 9b595d93c..25be6537b 100644
--- a/app/scripts/lib/nodeify.js
+++ b/app/scripts/lib/nodeify.js
@@ -1,6 +1,14 @@
const promiseToCallback = require('promise-to-callback')
const noop = function () {}
+/**
+ * A generator that returns a function which, when passed a promise, can treat that promise as a node style callback.
+ * The prime advantage being that callbacks are better for error handling.
+ *
+ * @param {Function} fn The function to handle as a callback
+ * @param {Object} context The context in which the fn is to be called, most often a this reference
+ *
+ */
module.exports = function nodeify (fn, context) {
return function () {
const args = [].slice.call(arguments)
diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js
index 6602f5aa8..43a7d0b42 100644
--- a/app/scripts/lib/personal-message-manager.js
+++ b/app/scripts/lib/personal-message-manager.js
@@ -3,6 +3,7 @@ const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util')
const createId = require('./random-id')
const hexRe = /^[0-9A-Fa-f]+$/g
+const log = require('loglevel')
module.exports = class PersonalMessageManager extends EventEmitter {
diff --git a/app/scripts/lib/port-stream.js b/app/scripts/lib/port-stream.js
index a9716fb00..5c4224fd9 100644
--- a/app/scripts/lib/port-stream.js
+++ b/app/scripts/lib/port-stream.js
@@ -6,6 +6,13 @@ module.exports = PortDuplexStream
inherits(PortDuplexStream, Duplex)
+/**
+ * Creates a stream that's both readable and writable.
+ * The stream supports arbitrary objects.
+ *
+ * @class
+ * @param {Object} port Remote Port object
+ */
function PortDuplexStream (port) {
Duplex.call(this, {
objectMode: true,
@@ -15,8 +22,13 @@ function PortDuplexStream (port) {
port.onDisconnect.addListener(this._onDisconnect.bind(this))
}
-// private
-
+/**
+ * Callback triggered when a message is received from
+ * the remote Port associated with this Stream.
+ *
+ * @private
+ * @param {Object} msg - Payload from the onMessage listener of Port
+ */
PortDuplexStream.prototype._onMessage = function (msg) {
if (Buffer.isBuffer(msg)) {
delete msg._isBuffer
@@ -27,14 +39,31 @@ PortDuplexStream.prototype._onMessage = function (msg) {
}
}
+/**
+ * Callback triggered when the remote Port
+ * associated with this Stream disconnects.
+ *
+ * @private
+ */
PortDuplexStream.prototype._onDisconnect = function () {
this.destroy()
}
-// stream plumbing
-
+/**
+ * Explicitly sets read operations to a no-op
+ */
PortDuplexStream.prototype._read = noop
+
+/**
+ * Called internally when data should be written to
+ * this writable stream.
+ *
+ * @private
+ * @param {*} msg Arbitrary object to write
+ * @param {string} encoding Encoding to use when writing payload
+ * @param {Function} cb Called when writing is complete or an error occurs
+ */
PortDuplexStream.prototype._write = function (msg, encoding, cb) {
try {
if (Buffer.isBuffer(msg)) {
diff --git a/app/scripts/lib/seed-phrase-verifier.js b/app/scripts/lib/seed-phrase-verifier.js
index 9cea22029..7ba712c0d 100644
--- a/app/scripts/lib/seed-phrase-verifier.js
+++ b/app/scripts/lib/seed-phrase-verifier.js
@@ -1,4 +1,5 @@
const KeyringController = require('eth-keyring-controller')
+const log = require('loglevel')
const seedPhraseVerifier = {
diff --git a/app/scripts/lib/setupMetamaskMeshMetrics.js b/app/scripts/lib/setupMetamaskMeshMetrics.js
index 40343f017..02690a948 100644
--- a/app/scripts/lib/setupMetamaskMeshMetrics.js
+++ b/app/scripts/lib/setupMetamaskMeshMetrics.js
@@ -1,6 +1,9 @@
module.exports = setupMetamaskMeshMetrics
+/**
+ * Injects an iframe into the current document for testing
+ */
function setupMetamaskMeshMetrics() {
const testingContainer = document.createElement('iframe')
testingContainer.src = 'https://metamask.github.io/mesh-testing/'
diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js
index 8bb0b4f3c..3dbc064b5 100644
--- a/app/scripts/lib/stream-utils.js
+++ b/app/scripts/lib/stream-utils.js
@@ -8,20 +8,34 @@ module.exports = {
setupMultiplex: setupMultiplex,
}
+/**
+ * Returns a stream transform that parses JSON strings passing through
+ * @return {stream.Transform}
+ */
function jsonParseStream () {
- return Through.obj(function (serialized, encoding, cb) {
+ return Through.obj(function (serialized, _, cb) {
this.push(JSON.parse(serialized))
cb()
})
}
+/**
+ * Returns a stream transform that calls {@code JSON.stringify}
+ * on objects passing through
+ * @return {stream.Transform} the stream transform
+ */
function jsonStringifyStream () {
- return Through.obj(function (obj, encoding, cb) {
+ return Through.obj(function (obj, _, cb) {
this.push(JSON.stringify(obj))
cb()
})
}
+/**
+ * Sets up stream multiplexing for the given stream
+ * @param {any} connectionStream - the stream to mux
+ * @return {stream.Stream} the multiplexed stream
+ */
function setupMultiplex (connectionStream) {
const mux = new ObjectMultiplex()
pump(
diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js
index d8ea17400..c6d10ee62 100644
--- a/app/scripts/lib/tx-state-manager.js
+++ b/app/scripts/lib/tx-state-manager.js
@@ -92,8 +92,10 @@ module.exports = class TransactionStateManager extends EventEmitter {
// or rejected tx's.
// not tx's that are pending or unapproved
if (txCount > txHistoryLimit - 1) {
- const index = transactions.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected')
- transactions.splice(index, 1)
+ let index = transactions.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected')
+ if (index !== -1) {
+ transactions.splice(index, 1)
+ }
}
transactions.push(txMeta)
this._saveTxList(transactions)
diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js
index 8b760790e..60042155e 100644
--- a/app/scripts/lib/typed-message-manager.js
+++ b/app/scripts/lib/typed-message-manager.js
@@ -3,7 +3,7 @@ const ObservableStore = require('obs-store')
const createId = require('./random-id')
const assert = require('assert')
const sigUtil = require('eth-sig-util')
-
+const log = require('loglevel')
module.exports = class TypedMessageManager extends EventEmitter {
constructor (opts) {
diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js
index 6dee9edf0..431d1e59c 100644
--- a/app/scripts/lib/util.js
+++ b/app/scripts/lib/util.js
@@ -1,20 +1,53 @@
const ethUtil = require('ethereumjs-util')
const assert = require('assert')
const BN = require('bn.js')
+const {
+ ENVIRONMENT_TYPE_POPUP,
+ ENVIRONMENT_TYPE_NOTIFICATION,
+ ENVIRONMENT_TYPE_FULLSCREEN,
+} = require('./enums')
-module.exports = {
- getStack,
- sufficientBalance,
- hexToBn,
- bnToHex,
- BnMultiplyByFraction,
-}
-
+/**
+ * Generates an example stack trace
+ *
+ * @returns {string} A stack trace
+ *
+ */
function getStack () {
const stack = new Error('Stack trace generator - not an error').stack
return stack
}
+/**
+ * Used to determine the window type through which the app is being viewed.
+ * - 'popup' refers to the extension opened through the browser app icon (in top right corner in chrome and firefox)
+ * - 'responsive' refers to the main browser window
+ * - 'notification' refers to the popup that appears in its own window when taking action outside of metamask
+ *
+ * @returns {string} A single word label that represents the type of window through which the app is being viewed
+ *
+ */
+const getEnvironmentType = (url = window.location.href) => {
+ if (url.match(/popup.html(?:\?.+)*$/)) {
+ return ENVIRONMENT_TYPE_POPUP
+ } else if (url.match(/home.html(?:\?.+)*$/) || url.match(/home.html(?:#.*)*$/)) {
+ return ENVIRONMENT_TYPE_FULLSCREEN
+ } else {
+ return ENVIRONMENT_TYPE_NOTIFICATION
+ }
+}
+
+/**
+ * Checks whether a given balance of ETH, represented as a hex string, is sufficient to pay a value plus a gas fee
+ *
+ * @param {object} txParams Contains data about a transaction
+ * @param {string} txParams.gas The gas for a transaction
+ * @param {string} txParams.gasPrice The price per gas for the transaction
+ * @param {string} txParams.value The value of ETH to send
+ * @param {string} hexBalance A balance of ETH represented as a hex string
+ * @returns {boolean} Whether the balance is greater than or equal to the value plus the value of gas times gasPrice
+ *
+ */
function sufficientBalance (txParams, hexBalance) {
// validate hexBalance is a hex string
assert.equal(typeof hexBalance, 'string', 'sufficientBalance - hexBalance is not a hex string')
@@ -29,16 +62,48 @@ function sufficientBalance (txParams, hexBalance) {
return balance.gte(maxCost)
}
+/**
+ * Converts a BN object to a hex string with a '0x' prefix
+ *
+ * @param {BN} inputBn The BN to convert to a hex string
+ * @returns {string} A '0x' prefixed hex string
+ *
+ */
function bnToHex (inputBn) {
return ethUtil.addHexPrefix(inputBn.toString(16))
}
+/**
+ * Converts a hex string to a BN object
+ *
+ * @param {string} inputHex A number represented as a hex string
+ * @returns {Object} A BN object
+ *
+ */
function hexToBn (inputHex) {
return new BN(ethUtil.stripHexPrefix(inputHex), 16)
}
+/**
+ * Used to multiply a BN by a fraction
+ *
+ * @param {BN} targetBN The number to multiply by a fraction
+ * @param {number|string} numerator The numerator of the fraction multiplier
+ * @param {number|string} denominator The denominator of the fraction multiplier
+ * @returns {BN} The product of the multiplication
+ *
+ */
function BnMultiplyByFraction (targetBN, numerator, denominator) {
const numBN = new BN(numerator)
const denomBN = new BN(denominator)
return targetBN.mul(numBN).div(denomBN)
}
+
+module.exports = {
+ getStack,
+ getEnvironmentType,
+ sufficientBalance,
+ hexToBn,
+ bnToHex,
+ BnMultiplyByFraction,
+}
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index b96acc9da..edde38819 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -5,10 +5,10 @@
*/
const EventEmitter = require('events')
-const extend = require('xtend')
const pump = require('pump')
const Dnode = require('dnode')
const ObservableStore = require('obs-store')
+const ComposableObservableStore = require('./lib/ComposableObservableStore')
const asStream = require('obs-store/lib/asStream')
const AccountTracker = require('./lib/account-tracker')
const RpcEngine = require('json-rpc-engine')
@@ -34,6 +34,7 @@ const PersonalMessageManager = require('./lib/personal-message-manager')
const TypedMessageManager = require('./lib/typed-message-manager')
const TransactionController = require('./controllers/transactions')
const BalancesController = require('./controllers/computed-balances')
+const TokenRatesController = require('./controllers/token-rates')
const ConfigManager = require('./lib/config-manager')
const nodeify = require('./lib/nodeify')
const accountImporter = require('./account-import-strategies')
@@ -44,6 +45,7 @@ const BN = require('ethereumjs-util').BN
const GWEI_BN = new BN('1000000000')
const percentile = require('percentile')
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
+const log = require('loglevel')
module.exports = class MetamaskController extends EventEmitter {
@@ -65,7 +67,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.platform = opts.platform
// observable state store
- this.store = new ObservableStore(initState)
+ this.store = new ComposableObservableStore(initState)
// lock to ensure only one vault created at once
this.createVaultMutex = new Mutex()
@@ -104,6 +106,11 @@ module.exports = class MetamaskController extends EventEmitter {
this.provider = this.initializeProvider()
this.blockTracker = this.provider._blockTracker
+ // token exchange rate tracker
+ this.tokenRatesController = new TokenRatesController({
+ preferences: this.preferencesController.store,
+ })
+
this.recentBlocksController = new RecentBlocksController({
blockTracker: this.blockTracker,
provider: this.provider,
@@ -184,53 +191,37 @@ module.exports = class MetamaskController extends EventEmitter {
this.typedMessageManager = new TypedMessageManager()
this.publicConfigStore = this.initPublicConfigStore()
- // manual disk state subscriptions
- this.txController.store.subscribe((state) => {
- this.store.updateState({ TransactionController: state })
- })
- this.keyringController.store.subscribe((state) => {
- this.store.updateState({ KeyringController: state })
- })
- this.preferencesController.store.subscribe((state) => {
- this.store.updateState({ PreferencesController: state })
- })
- this.addressBookController.store.subscribe((state) => {
- this.store.updateState({ AddressBookController: state })
- })
- this.currencyController.store.subscribe((state) => {
- this.store.updateState({ CurrencyController: state })
- })
- this.noticeController.store.subscribe((state) => {
- this.store.updateState({ NoticeController: state })
- })
- this.shapeshiftController.store.subscribe((state) => {
- this.store.updateState({ ShapeShiftController: state })
- })
- this.networkController.store.subscribe((state) => {
- this.store.updateState({ NetworkController: state })
+ this.store.updateStructure({
+ TransactionController: this.txController.store,
+ KeyringController: this.keyringController.store,
+ PreferencesController: this.preferencesController.store,
+ AddressBookController: this.addressBookController.store,
+ CurrencyController: this.currencyController.store,
+ NoticeController: this.noticeController.store,
+ ShapeShiftController: this.shapeshiftController.store,
+ NetworkController: this.networkController.store,
+ InfuraController: this.infuraController.store,
})
- this.infuraController.store.subscribe((state) => {
- this.store.updateState({ InfuraController: state })
+ this.memStore = new ComposableObservableStore(null, {
+ NetworkController: this.networkController.store,
+ AccountTracker: this.accountTracker.store,
+ TxController: this.txController.memStore,
+ BalancesController: this.balancesController.store,
+ TokenRatesController: this.tokenRatesController.store,
+ MessageManager: this.messageManager.memStore,
+ PersonalMessageManager: this.personalMessageManager.memStore,
+ TypesMessageManager: this.typedMessageManager.memStore,
+ KeyringController: this.keyringController.memStore,
+ PreferencesController: this.preferencesController.store,
+ RecentBlocksController: this.recentBlocksController.store,
+ AddressBookController: this.addressBookController.store,
+ CurrencyController: this.currencyController.store,
+ NoticeController: this.noticeController.memStore,
+ ShapeshiftController: this.shapeshiftController.store,
+ InfuraController: this.infuraController.store,
})
-
- // manual mem state subscriptions
- const sendUpdate = this.sendUpdate.bind(this)
- this.networkController.store.subscribe(sendUpdate)
- this.accountTracker.store.subscribe(sendUpdate)
- this.txController.memStore.subscribe(sendUpdate)
- this.balancesController.store.subscribe(sendUpdate)
- this.messageManager.memStore.subscribe(sendUpdate)
- this.personalMessageManager.memStore.subscribe(sendUpdate)
- this.typedMessageManager.memStore.subscribe(sendUpdate)
- this.keyringController.memStore.subscribe(sendUpdate)
- this.preferencesController.store.subscribe(sendUpdate)
- this.recentBlocksController.store.subscribe(sendUpdate)
- this.addressBookController.store.subscribe(sendUpdate)
- this.currencyController.store.subscribe(sendUpdate)
- this.noticeController.memStore.subscribe(sendUpdate)
- this.shapeshiftController.store.subscribe(sendUpdate)
- this.infuraController.store.subscribe(sendUpdate)
+ this.memStore.subscribe(this.sendUpdate.bind(this))
}
/**
@@ -272,6 +263,7 @@ module.exports = class MetamaskController extends EventEmitter {
/**
* Constructor helper: initialize a public config store.
+ * This store is used to make some config info available to Dapps synchronously.
*/
initPublicConfigStore () {
// get init state
@@ -279,6 +271,7 @@ module.exports = class MetamaskController extends EventEmitter {
// memStore -> transform -> publicConfigStore
this.on('update', (memState) => {
+ this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen
const publicState = selectPublicState(memState)
publicConfigStore.putState(publicState)
})
@@ -308,39 +301,25 @@ module.exports = class MetamaskController extends EventEmitter {
const vault = this.keyringController.store.getState().vault
const isInitialized = (!!wallet || !!vault)
- return extend(
- {
- isInitialized,
- },
- this.networkController.store.getState(),
- this.accountTracker.store.getState(),
- this.txController.memStore.getState(),
- this.messageManager.memStore.getState(),
- this.personalMessageManager.memStore.getState(),
- this.typedMessageManager.memStore.getState(),
- this.keyringController.memStore.getState(),
- this.balancesController.store.getState(),
- this.preferencesController.store.getState(),
- this.addressBookController.store.getState(),
- this.currencyController.store.getState(),
- this.noticeController.memStore.getState(),
- this.infuraController.store.getState(),
- this.recentBlocksController.store.getState(),
- // config manager
- this.configManager.getConfig(),
- this.shapeshiftController.store.getState(),
- {
+ return {
+ ...{ isInitialized },
+ ...this.memStore.getFlatState(),
+ ...this.configManager.getConfig(),
+ ...{
lostAccounts: this.configManager.getLostAccounts(),
seedWords: this.configManager.getSeedWords(),
forgottenPassword: this.configManager.getPasswordForgotten(),
- }
- )
+ isRevealingSeedWords: Boolean(this.configManager.getIsRevealingSeedWords()),
+ },
+ }
}
/**
- * Returns an api-object which is consumed by the UI
+ * Returns an Object containing API Callback Functions.
+ * These functions are the interface for the UI.
+ * The API object can be transmitted over a stream with dnode.
*
- * @returns {Object}
+ * @returns {Object} Object containing API functions.
*/
getApi () {
const keyringController = this.keyringController
@@ -372,6 +351,7 @@ module.exports = class MetamaskController extends EventEmitter {
clearSeedWordCache: this.clearSeedWordCache.bind(this),
resetAccount: nodeify(this.resetAccount, this),
importAccountWithStrategy: this.importAccountWithStrategy.bind(this),
+ setIsRevealingSeedWords: this.configManager.setIsRevealingSeedWords.bind(this.configManager),
// vault management
submitPassword: nodeify(keyringController.submitPassword, keyringController),
@@ -430,16 +410,18 @@ module.exports = class MetamaskController extends EventEmitter {
//=============================================================================
/**
- * Creates a new Vault(?) and create a new keychain(?)
- *
- * A vault is ...
+ * Creates a new Vault and create a new keychain.
*
- * A keychain is ...
+ * A vault, or KeyringController, is a controller that contains
+ * many different account strategies, currently called Keyrings.
+ * Creating it new means wiping all previous keyrings.
*
+ * A keychain, or keyring, controls many accounts with a single backup and signing strategy.
+ * For example, a mnemonic phrase can generate many accounts, and is a keyring.
*
- * @param {} password
+ * @param {string} password
*
- * @returns {} vault
+ * @returns {Object} vault
*/
async createNewVaultAndKeychain (password) {
const release = await this.createVaultMutex.acquire()
@@ -465,7 +447,7 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
- * Create a new Vault and restore an existent keychain
+ * Create a new Vault and restore an existent keyring.
* @param {} password
* @param {} seed
*/
@@ -483,10 +465,16 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
+ * @type Identity
+ * @property {string} name - The account nickname.
+ * @property {string} address - The account's ethereum address, in lower case.
+ * @property {boolean} mayBeFauceting - Whether this account is currently
+ * receiving funds from our automatic Ropsten faucet.
+ */
+
+ /**
* Retrieves the first Identiy from the passed Vault and selects the related address
*
- * An Identity is ...
- *
* @param {} vault
*/
selectFirstIdentity (vault) {
@@ -495,12 +483,12 @@ module.exports = class MetamaskController extends EventEmitter {
this.preferencesController.setSelectedAddress(address)
}
- // ?
- // Opinionated Keyring Management
+ //
+ // Account Management
//
/**
- * Adds a new account to ...
+ * Adds a new account to the default (first) HD seed phrase Keyring.
*
* @returns {} keyState
*/
@@ -530,6 +518,8 @@ module.exports = class MetamaskController extends EventEmitter {
*
* Used when creating a first vault, to allow confirmation.
* Also used when revealing the seed words in the confirmation view.
+ *
+ * @param {Function} cb - A callback called on completion.
*/
placeSeedWords (cb) {
@@ -549,6 +539,8 @@ module.exports = class MetamaskController extends EventEmitter {
* Validity: seed phrase restores the accounts belonging to the current vault.
*
* Called when the first account is created and on unlocking the vault.
+ *
+ * @returns {Promise<string>} Seed phrase to be confirmed by the user.
*/
async verifySeedPhrase () {
@@ -579,6 +571,7 @@ module.exports = class MetamaskController extends EventEmitter {
*
* The seed phrase remains available in the background process.
*
+ * @param {function} cb Callback function called with the current address.
*/
clearSeedWordCache (cb) {
this.configManager.setSeedWords(null)
@@ -586,9 +579,13 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
- * ?
+ * Clears the transaction history, to allow users to force-reset their nonces.
+ * Mostly used in development environments, when networks are restarted with
+ * the same network ID.
+ *
+ * @returns Promise<string> The current selected address.
*/
- async resetAccount (cb) {
+ async resetAccount () {
const selectedAddress = this.preferencesController.getSelectedAddress()
this.txController.wipeTransactions(selectedAddress)
@@ -600,11 +597,13 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
- * Imports an account ... ?
+ * Imports an account with the specified import strategy.
+ * These are defined in app/scripts/account-import-strategies
+ * Each strategy represents a different way of serializing an Ethereum key pair.
*
- * @param {} strategy
- * @param {} args
- * @param {} cb
+ * @param {string} strategy - A unique identifier for an account import strategy.
+ * @param {any} args - The data required by that strategy to import an account.
+ * @param {Function} cb - A callback function called with a state update on success.
*/
importAccountWithStrategy (strategy, args, cb) {
accountImporter.importAccount(strategy, args)
@@ -618,13 +617,42 @@ module.exports = class MetamaskController extends EventEmitter {
}
// ---------------------------------------------------------------------------
- // Identity Management (sign)
+ // Identity Management (signature operations)
+
+ // eth_sign methods:
+
+ /**
+ * Called when a Dapp uses the eth_sign method, to request user approval.
+ * eth_sign is a pure signature of arbitrary data. It is on a deprecation
+ * path, since this data can be a transaction, or can leak private key
+ * information.
+ *
+ * @param {Object} msgParams - The params passed to eth_sign.
+ * @param {Function} cb = The callback function called with the signature.
+ */
+ newUnsignedMessage (msgParams, cb) {
+ const msgId = this.messageManager.addUnapprovedMessage(msgParams)
+ this.sendUpdate()
+ this.opts.showUnconfirmedMessage()
+ this.messageManager.once(`${msgId}:finished`, (data) => {
+ switch (data.status) {
+ case 'signed':
+ return cb(null, data.rawSig)
+ case 'rejected':
+ return cb(new Error('MetaMask Message Signature: User denied message signature.'))
+ default:
+ return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
+ }
+ })
+ }
/**
- * @param {} msgParams
- * @param {} cb
+ * Signifies user intent to complete an eth_sign method.
+ *
+ * @param {Object} msgParams The params passed to eth_call.
+ * @returns {Promise<Object>} Full state update.
*/
- signMessage (msgParams, cb) {
+ signMessage (msgParams) {
log.info('MetaMaskController - signMessage')
const msgId = msgParams.metamaskId
@@ -643,14 +671,37 @@ module.exports = class MetamaskController extends EventEmitter {
})
}
- // Prefixed Style Message Signing Methods:
+ /**
+ * Used to cancel a message submitted via eth_sign.
+ *
+ * @param {string} msgId - The id of the message to cancel.
+ */
+ cancelMessage (msgId, cb) {
+ const messageManager = this.messageManager
+ messageManager.rejectMsg(msgId)
+ if (cb && typeof cb === 'function') {
+ cb(null, this.getState())
+ }
+ }
+
+ // personal_sign methods:
/**
+ * Called when a dapp uses the personal_sign method.
+ * This is identical to the Geth eth_sign method, and may eventually replace
+ * eth_sign.
*
- * @param {} msgParams
- * @param {} cb
+ * We currently define our eth_sign and personal_sign mostly for legacy Dapps.
+ *
+ * @param {Object} msgParams - The params of the message to sign & return to the Dapp.
+ * @param {Function} cb - The callback function called with the signature.
+ * Passed back to the requesting Dapp.
*/
- approvePersonalMessage (msgParams, cb) {
+ newUnsignedPersonalMessage (msgParams, cb) {
+ if (!msgParams.from) {
+ return cb(new Error('MetaMask Message Signature: from field is required.'))
+ }
+
const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
this.sendUpdate()
this.opts.showUnconfirmedMessage()
@@ -659,7 +710,7 @@ module.exports = class MetamaskController extends EventEmitter {
case 'signed':
return cb(null, data.rawSig)
case 'rejected':
- return cb(new Error('MetaMask Message Signature: User denied transaction signature.'))
+ return cb(new Error('MetaMask Message Signature: User denied message signature.'))
default:
return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
}
@@ -667,7 +718,11 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
- * @param {} msgParams
+ * Signifies a user's approval to sign a personal_sign message in queue.
+ * Triggers signing, and the callback function from newUnsignedPersonalMessage.
+ *
+ * @param {Object} msgParams - The params of the message to sign & return to the Dapp.
+ * @returns {Promise<Object>} - A full state update.
*/
signPersonalMessage (msgParams) {
log.info('MetaMaskController - signPersonalMessage')
@@ -688,7 +743,54 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
- * @param {} msgParams
+ * Used to cancel a personal_sign type message.
+ * @param {string} msgId - The ID of the message to cancel.
+ * @param {Function} cb - The callback function called with a full state update.
+ */
+ cancelPersonalMessage (msgId, cb) {
+ const messageManager = this.personalMessageManager
+ messageManager.rejectMsg(msgId)
+ if (cb && typeof cb === 'function') {
+ cb(null, this.getState())
+ }
+ }
+
+ // eth_signTypedData methods
+
+ /**
+ * Called when a dapp uses the eth_signTypedData method, per EIP 712.
+ *
+ * @param {Object} msgParams - The params passed to eth_signTypedData.
+ * @param {Function} cb - The callback function, called with the signature.
+ */
+ newUnsignedTypedMessage (msgParams, cb) {
+ let msgId
+ try {
+ msgId = this.typedMessageManager.addUnapprovedMessage(msgParams)
+ this.sendUpdate()
+ this.opts.showUnconfirmedMessage()
+ } catch (e) {
+ return cb(e)
+ }
+
+ this.typedMessageManager.once(`${msgId}:finished`, (data) => {
+ switch (data.status) {
+ case 'signed':
+ return cb(null, data.rawSig)
+ case 'rejected':
+ return cb(new Error('MetaMask Message Signature: User denied message signature.'))
+ default:
+ return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
+ }
+ })
+ }
+
+ /**
+ * The method for a user approving a call to eth_signTypedData, per EIP 712.
+ * Triggers the callback in newUnsignedTypedMessage.
+ *
+ * @param {Object} msgParams - The params passed to eth_signTypedData.
+ * @returns {Object} Full state update.
*/
signTypedMessage (msgParams) {
log.info('MetaMaskController - signTypedMessage')
@@ -708,12 +810,30 @@ module.exports = class MetamaskController extends EventEmitter {
})
}
+ /**
+ * Used to cancel a eth_signTypedData type message.
+ * @param {string} msgId - The ID of the message to cancel.
+ * @param {Function} cb - The callback function called with a full state update.
+ */
+ cancelTypedMessage (msgId, cb) {
+ const messageManager = this.typedMessageManager
+ messageManager.rejectMsg(msgId)
+ if (cb && typeof cb === 'function') {
+ cb(null, this.getState())
+ }
+ }
+
// ---------------------------------------------------------------------------
- // Account Restauration
+ // MetaMask Version 3 Migration Account Restauration Methods
/**
- * ?
+ * A legacy method (probably dead code) that was used when we swapped out our
+ * key management library that we depended on.
*
+ * Described in:
+ * https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd
+ *
+ * @deprecated
* @param {} migratorOutput
*/
restoreOldVaultAccounts (migratorOutput) {
@@ -723,8 +843,26 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
- * ?
+ * A legacy method used to record user confirmation that they understand
+ * that some of their accounts have been recovered but should be backed up.
+ *
+ * @deprecated
+ * @param {Function} cb - A callback function called with a full state update.
+ */
+ markAccountsFound (cb) {
+ this.configManager.setLostAccounts([])
+ this.sendUpdate()
+ cb(null, this.getState())
+ }
+
+ /**
+ * A legacy method (probably dead code) that was used when we swapped out our
+ * key management library that we depended on.
+ *
+ * Described in:
+ * https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd
*
+ * @deprecated
* @param {} migratorOutput
*/
restoreOldLostAccounts (migratorOutput) {
@@ -737,12 +875,23 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
- * Import (lost) Accounts
+ * An account object
+ * @typedef Account
+ * @property string privateKey - The private key of the account.
+ */
+
+ /**
+ * Probably no longer needed, related to the Version 3 migration.
+ * Imports a hash of accounts to private keys into the vault.
*
- * @param {Object} {lostAccounts} @Array accounts <{ address, privateKey }>
+ * Described in:
+ * https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd
*
* Uses the array's private keys to create a new Simple Key Pair keychain
* and add it to the keyring controller.
+ * @deprecated
+ * @param {Account[]} lostAccounts -
+ * @returns {Keyring[]} An array of the restored keyrings.
*/
importLostAccounts ({ lostAccounts }) {
const privKeys = lostAccounts.map(acct => acct.privateKey)
@@ -756,113 +905,37 @@ module.exports = class MetamaskController extends EventEmitter {
// END (VAULT / KEYRING RELATED METHODS)
//=============================================================================
-//
-
-//=============================================================================
-// MESSAGES
-//=============================================================================
-
+ /**
+ * Allows a user to try to speed up a transaction by retrying it
+ * with higher gas.
+ *
+ * @param {string} txId - The ID of the transaction to speed up.
+ * @param {Function} cb - The callback function called with a full state update.
+ */
async retryTransaction (txId, cb) {
await this.txController.retryTransaction(txId)
const state = await this.getState()
return state
}
+//=============================================================================
+// PASSWORD MANAGEMENT
+//=============================================================================
- newUnsignedMessage (msgParams, cb) {
- const msgId = this.messageManager.addUnapprovedMessage(msgParams)
- this.sendUpdate()
- this.opts.showUnconfirmedMessage()
- this.messageManager.once(`${msgId}:finished`, (data) => {
- switch (data.status) {
- case 'signed':
- return cb(null, data.rawSig)
- case 'rejected':
- return cb(new Error('MetaMask Message Signature: User denied message signature.'))
- default:
- return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
- }
- })
- }
-
- newUnsignedPersonalMessage (msgParams, cb) {
- if (!msgParams.from) {
- return cb(new Error('MetaMask Message Signature: from field is required.'))
- }
-
- const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
- this.sendUpdate()
- this.opts.showUnconfirmedMessage()
- this.personalMessageManager.once(`${msgId}:finished`, (data) => {
- switch (data.status) {
- case 'signed':
- return cb(null, data.rawSig)
- case 'rejected':
- return cb(new Error('MetaMask Message Signature: User denied message signature.'))
- default:
- return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
- }
- })
- }
-
- newUnsignedTypedMessage (msgParams, cb) {
- let msgId
- try {
- msgId = this.typedMessageManager.addUnapprovedMessage(msgParams)
- this.sendUpdate()
- this.opts.showUnconfirmedMessage()
- } catch (e) {
- return cb(e)
- }
-
- this.typedMessageManager.once(`${msgId}:finished`, (data) => {
- switch (data.status) {
- case 'signed':
- return cb(null, data.rawSig)
- case 'rejected':
- return cb(new Error('MetaMask Message Signature: User denied message signature.'))
- default:
- return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
- }
- })
- }
-
- cancelMessage (msgId, cb) {
- const messageManager = this.messageManager
- messageManager.rejectMsg(msgId)
- if (cb && typeof cb === 'function') {
- cb(null, this.getState())
- }
- }
-
- cancelPersonalMessage (msgId, cb) {
- const messageManager = this.personalMessageManager
- messageManager.rejectMsg(msgId)
- if (cb && typeof cb === 'function') {
- cb(null, this.getState())
- }
- }
-
- cancelTypedMessage (msgId, cb) {
- const messageManager = this.typedMessageManager
- messageManager.rejectMsg(msgId)
- if (cb && typeof cb === 'function') {
- cb(null, this.getState())
- }
- }
-
- markAccountsFound (cb) {
- this.configManager.setLostAccounts([])
- this.sendUpdate()
- cb(null, this.getState())
- }
-
+ /**
+ * Allows a user to begin the seed phrase recovery process.
+ * @param {Function} cb - A callback function called when complete.
+ */
markPasswordForgotten(cb) {
this.configManager.setPasswordForgotten(true)
this.sendUpdate()
cb()
}
+ /**
+ * Allows a user to end the seed phrase recovery process.
+ * @param {Function} cb - A callback function called when complete.
+ */
unMarkPasswordForgotten(cb) {
this.configManager.setPasswordForgotten(false)
this.sendUpdate()
@@ -873,6 +946,13 @@ module.exports = class MetamaskController extends EventEmitter {
// SETUP
//=============================================================================
+ /**
+ * Used to create a multiplexed stream for connecting to an untrusted context
+ * like a Dapp or other extension.
+ * @param {*} connectionStream - The Duplex stream to connect to.
+ * @param {string} originDomain - The domain requesting the stream, which
+ * may trigger a blacklist reload.
+ */
setupUntrustedCommunication (connectionStream, originDomain) {
// Check if new connection is blacklisted
if (this.blacklistController.checkForPhishing(originDomain)) {
@@ -888,6 +968,16 @@ module.exports = class MetamaskController extends EventEmitter {
this.setupPublicConfig(mux.createStream('publicConfig'))
}
+ /**
+ * Used to create a multiplexed stream for connecting to a trusted context,
+ * like our own user interfaces, which have the provider APIs, but also
+ * receive the exported API from this controller, which includes trusted
+ * functions, like the ability to approve transactions or sign messages.
+ *
+ * @param {*} connectionStream - The duplex stream to connect to.
+ * @param {string} originDomain - The domain requesting the connection,
+ * used in logging and error reporting.
+ */
setupTrustedCommunication (connectionStream, originDomain) {
// setup multiplexing
const mux = setupMultiplex(connectionStream)
@@ -896,12 +986,25 @@ module.exports = class MetamaskController extends EventEmitter {
this.setupProviderConnection(mux.createStream('provider'), originDomain)
}
+ /**
+ * Called when we detect a suspicious domain. Requests the browser redirects
+ * to our anti-phishing page.
+ *
+ * @private
+ * @param {*} connectionStream - The duplex stream to the per-page script,
+ * for sending the reload attempt to.
+ * @param {string} hostname - The URL that triggered the suspicion.
+ */
sendPhishingWarning (connectionStream, hostname) {
const mux = setupMultiplex(connectionStream)
const phishingStream = mux.createStream('phishing')
phishingStream.write({ hostname })
}
+ /**
+ * A method for providing our API over a stream using Dnode.
+ * @param {*} outStream - The stream to provide our API over.
+ */
setupControllerConnection (outStream) {
const api = this.getApi()
const dnode = Dnode(api)
@@ -920,6 +1023,11 @@ module.exports = class MetamaskController extends EventEmitter {
})
}
+ /**
+ * A method for serving our ethereum provider over a given stream.
+ * @param {*} outStream - The stream to provide over.
+ * @param {string} origin - The URI of the requesting resource.
+ */
setupProviderConnection (outStream, origin) {
// setup json rpc engine stack
const engine = new RpcEngine()
@@ -949,6 +1057,16 @@ module.exports = class MetamaskController extends EventEmitter {
)
}
+ /**
+ * A method for providing our public config info over a stream.
+ * This includes info we like to be synchronous if possible, like
+ * the current selected account, and network ID.
+ *
+ * Since synchronous methods have been deprecated in web3,
+ * this is a good candidate for deprecation.
+ *
+ * @param {*} outStream - The stream to provide public config over.
+ */
setupPublicConfig (outStream) {
pump(
asStream(this.publicConfigStore),
@@ -959,10 +1077,21 @@ module.exports = class MetamaskController extends EventEmitter {
)
}
+ /**
+ * A method for emitting the full MetaMask state to all registered listeners.
+ * @private
+ */
privateSendUpdate () {
this.emit('update', this.getState())
}
+ /**
+ * A method for estimating a good gas price at recent prices.
+ * Returns the lowest price that would have been included in
+ * 50% of recent blocks.
+ *
+ * @returns {string} A hex representation of the suggested wei gas price.
+ */
getGasPrice () {
const { recentBlocksController } = this
const { recentBlocks } = recentBlocksController.store.getState()
@@ -996,6 +1125,11 @@ module.exports = class MetamaskController extends EventEmitter {
// Log blocks
+ /**
+ * A method for setting the user's preferred display currency.
+ * @param {string} currencyCode - The code of the preferred currency.
+ * @param {Function} cb - A callback function returning currency info.
+ */
setCurrentCurrency (currencyCode, cb) {
try {
this.currencyController.setCurrentCurrency(currencyCode)
@@ -1011,6 +1145,13 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
+ /**
+ * A method for forwarding the user to the easiest way to obtain ether,
+ * or the network "gas" currency, for the current selected network.
+ *
+ * @param {string} address - The address to fund.
+ * @param {string} amount - The amount of ether desired, as a base 10 string.
+ */
buyEth (address, amount) {
if (!amount) amount = '5'
const network = this.networkController.getNetworkState()
@@ -1018,18 +1159,33 @@ module.exports = class MetamaskController extends EventEmitter {
if (url) this.platform.openWindow({ url })
}
+ /**
+ * A method for triggering a shapeshift currency transfer.
+ * @param {string} depositAddress - The address to deposit to.
+ * @property {string} depositType - An abbreviation of the type of crypto currency to be deposited.
+ */
createShapeShiftTx (depositAddress, depositType) {
this.shapeshiftController.createShapeShiftTx(depositAddress, depositType)
}
// network
- async setCustomRpc (rpcTarget, rpcList) {
+ /**
+ * A method for selecting a custom URL for an ethereum RPC provider.
+ * @param {string} rpcTarget - A URL for a valid Ethereum RPC API.
+ * @returns {Promise<String>} - The RPC Target URL confirmed.
+ */
+ async setCustomRpc (rpcTarget) {
this.networkController.setRpcTarget(rpcTarget)
await this.preferencesController.updateFrequentRpcList(rpcTarget)
return rpcTarget
}
+ /**
+ * Sets whether or not to use the blockie identicon format.
+ * @param {boolean} val - True for bockie, false for jazzicon.
+ * @param {Function} cb - A callback function called when complete.
+ */
setUseBlockie (val, cb) {
try {
this.preferencesController.setUseBlockie(val)
@@ -1039,6 +1195,11 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
+ /**
+ * A method for setting a user's current locale, affecting the language rendered.
+ * @param {string} key - Locale identifier.
+ * @param {Function} cb - A callback function called when complete.
+ */
setCurrentLocale (key, cb) {
try {
this.preferencesController.setCurrentLocale(key)
@@ -1048,6 +1209,11 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
+ /**
+ * A method for initializing storage the first time.
+ * @param {Object} initState - The default state to initialize with.
+ * @private
+ */
recordFirstTimeInfo (initState) {
if (!('firstTimeInfo' in initState)) {
initState.firstTimeInfo = {
@@ -1057,4 +1223,22 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
+ /**
+ * A method for recording whether the MetaMask user interface is open or not.
+ * @private
+ * @param {boolean} open
+ */
+ set isClientOpen (open) {
+ this._isClientOpen = open
+ this.isClientOpenAndUnlocked = this.getState().isUnlocked && open
+ }
+
+ /**
+ * A method for activating the retrieval of price data, which should only be fetched when the UI is visible.
+ * @private
+ * @param {boolean} active - True if price data should be getting fetched.
+ */
+ set isClientOpenAndUnlocked (active) {
+ this.tokenRatesController.isActive = active
+ }
}
diff --git a/app/scripts/migrations/013.js b/app/scripts/migrations/013.js
index 8f11e510e..15a9b28d4 100644
--- a/app/scripts/migrations/013.js
+++ b/app/scripts/migrations/013.js
@@ -27,8 +27,11 @@ module.exports = {
function transformState (state) {
const newState = state
- if (newState.config.provider.type === 'testnet') {
- newState.config.provider.type = 'ropsten'
+ const { config } = newState
+ if ( config && config.provider ) {
+ if (config.provider.type === 'testnet') {
+ newState.config.provider.type = 'ropsten'
+ }
}
return newState
}
diff --git a/app/scripts/migrations/015.js b/app/scripts/migrations/015.js
index 4b839580b..5e2f9e63b 100644
--- a/app/scripts/migrations/015.js
+++ b/app/scripts/migrations/015.js
@@ -28,11 +28,14 @@ module.exports = {
function transformState (state) {
const newState = state
- const transactions = newState.TransactionController.transactions
- newState.TransactionController.transactions = transactions.map((txMeta) => {
- if (!txMeta.err) return txMeta
- else if (txMeta.err.message === 'Gave up submitting tx.') txMeta.status = 'failed'
- return txMeta
- })
+ const { TransactionController } = newState
+ if (TransactionController && TransactionController.transactions) {
+ const transactions = TransactionController.transactions
+ newState.TransactionController.transactions = transactions.map((txMeta) => {
+ if (!txMeta.err) return txMeta
+ else if (txMeta.err.message === 'Gave up submitting tx.') txMeta.status = 'failed'
+ return txMeta
+ })
+ }
return newState
}
diff --git a/app/scripts/migrations/016.js b/app/scripts/migrations/016.js
index 4fc534f1c..048c7a40e 100644
--- a/app/scripts/migrations/016.js
+++ b/app/scripts/migrations/016.js
@@ -28,14 +28,18 @@ module.exports = {
function transformState (state) {
const newState = state
- const transactions = newState.TransactionController.transactions
- newState.TransactionController.transactions = transactions.map((txMeta) => {
- if (!txMeta.err) return txMeta
- if (txMeta.err === 'transaction with the same hash was already imported.') {
- txMeta.status = 'submitted'
- delete txMeta.err
- }
- return txMeta
- })
+ const { TransactionController } = newState
+ if (TransactionController && TransactionController.transactions) {
+ const transactions = newState.TransactionController.transactions
+
+ newState.TransactionController.transactions = transactions.map((txMeta) => {
+ if (!txMeta.err) return txMeta
+ if (txMeta.err === 'transaction with the same hash was already imported.') {
+ txMeta.status = 'submitted'
+ delete txMeta.err
+ }
+ return txMeta
+ })
+ }
return newState
}
diff --git a/app/scripts/migrations/017.js b/app/scripts/migrations/017.js
index 24959cd3a..5f6d906d6 100644
--- a/app/scripts/migrations/017.js
+++ b/app/scripts/migrations/017.js
@@ -27,14 +27,17 @@ module.exports = {
function transformState (state) {
const newState = state
- const transactions = newState.TransactionController.transactions
- newState.TransactionController.transactions = transactions.map((txMeta) => {
- if (!txMeta.status === 'failed') return txMeta
- if (txMeta.retryCount > 0 && txMeta.retryCount < 2) {
- txMeta.status = 'submitted'
- delete txMeta.err
- }
- return txMeta
- })
+ const { TransactionController } = newState
+ if (TransactionController && TransactionController.transactions) {
+ const transactions = newState.TransactionController.transactions
+ newState.TransactionController.transactions = transactions.map((txMeta) => {
+ if (!txMeta.status === 'failed') return txMeta
+ if (txMeta.retryCount > 0 && txMeta.retryCount < 2) {
+ txMeta.status = 'submitted'
+ delete txMeta.err
+ }
+ return txMeta
+ })
+ }
return newState
}
diff --git a/app/scripts/migrations/018.js b/app/scripts/migrations/018.js
index d27fe3f46..bea1fe3da 100644
--- a/app/scripts/migrations/018.js
+++ b/app/scripts/migrations/018.js
@@ -29,24 +29,27 @@ module.exports = {
function transformState (state) {
const newState = state
- const transactions = newState.TransactionController.transactions
- newState.TransactionController.transactions = transactions.map((txMeta) => {
- // no history: initialize
- if (!txMeta.history || txMeta.history.length === 0) {
- const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
- txMeta.history = [snapshot]
+ const { TransactionController } = newState
+ if (TransactionController && TransactionController.transactions) {
+ const transactions = newState.TransactionController.transactions
+ newState.TransactionController.transactions = transactions.map((txMeta) => {
+ // no history: initialize
+ if (!txMeta.history || txMeta.history.length === 0) {
+ const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
+ txMeta.history = [snapshot]
+ return txMeta
+ }
+ // has history: migrate
+ const newHistory = (
+ txStateHistoryHelper.migrateFromSnapshotsToDiffs(txMeta.history)
+ // remove empty diffs
+ .filter((entry) => {
+ return !Array.isArray(entry) || entry.length > 0
+ })
+ )
+ txMeta.history = newHistory
return txMeta
- }
- // has history: migrate
- const newHistory = (
- txStateHistoryHelper.migrateFromSnapshotsToDiffs(txMeta.history)
- // remove empty diffs
- .filter((entry) => {
- return !Array.isArray(entry) || entry.length > 0
- })
- )
- txMeta.history = newHistory
- return txMeta
- })
+ })
+ }
return newState
}
diff --git a/app/scripts/migrations/019.js b/app/scripts/migrations/019.js
index 072c96370..ce5da6859 100644
--- a/app/scripts/migrations/019.js
+++ b/app/scripts/migrations/019.js
@@ -29,32 +29,36 @@ module.exports = {
function transformState (state) {
const newState = state
- const transactions = newState.TransactionController.transactions
+ const { TransactionController } = newState
+ if (TransactionController && TransactionController.transactions) {
- newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => {
- if (txMeta.status !== 'submitted') return txMeta
+ const transactions = newState.TransactionController.transactions
- const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed')
- .filter((tx) => tx.txParams.from === txMeta.txParams.from)
- .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
- const highestConfirmedNonce = getHighestNonce(confirmedTxs)
+ newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => {
+ if (txMeta.status !== 'submitted') return txMeta
- const pendingTxs = txList.filter((tx) => tx.status === 'submitted')
- .filter((tx) => tx.txParams.from === txMeta.txParams.from)
- .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
- const highestContinuousNonce = getHighestContinuousFrom(pendingTxs, highestConfirmedNonce)
+ const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed')
+ .filter((tx) => tx.txParams.from === txMeta.txParams.from)
+ .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
+ const highestConfirmedNonce = getHighestNonce(confirmedTxs)
- const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce)
+ const pendingTxs = txList.filter((tx) => tx.status === 'submitted')
+ .filter((tx) => tx.txParams.from === txMeta.txParams.from)
+ .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
+ const highestContinuousNonce = getHighestContinuousFrom(pendingTxs, highestConfirmedNonce)
- if (parseInt(txMeta.txParams.nonce, 16) > maxNonce + 1) {
- txMeta.status = 'failed'
- txMeta.err = {
- message: 'nonce too high',
- note: 'migration 019 custom error',
+ const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce)
+
+ if (parseInt(txMeta.txParams.nonce, 16) > maxNonce + 1) {
+ txMeta.status = 'failed'
+ txMeta.err = {
+ message: 'nonce too high',
+ note: 'migration 019 custom error',
+ }
}
- }
- return txMeta
- })
+ return txMeta
+ })
+ }
return newState
}
diff --git a/app/scripts/migrations/022.js b/app/scripts/migrations/022.js
index c3c0d53ef..1fbe241e6 100644
--- a/app/scripts/migrations/022.js
+++ b/app/scripts/migrations/022.js
@@ -28,12 +28,15 @@ module.exports = {
function transformState (state) {
const newState = state
- const transactions = newState.TransactionController.transactions
-
- newState.TransactionController.transactions = transactions.map((txMeta) => {
- if (txMeta.status !== 'submitted' || txMeta.submittedTime) return txMeta
- txMeta.submittedTime = (new Date()).getTime()
- return txMeta
- })
+ const { TransactionController } = newState
+ if (TransactionController && TransactionController.transactions) {
+ const transactions = newState.TransactionController.transactions
+
+ newState.TransactionController.transactions = transactions.map((txMeta) => {
+ if (txMeta.status !== 'submitted' || txMeta.submittedTime) return txMeta
+ txMeta.submittedTime = (new Date()).getTime()
+ return txMeta
+ })
+ }
return newState
}
diff --git a/app/scripts/migrations/023.js b/app/scripts/migrations/023.js
index bce0a5bea..151496b06 100644
--- a/app/scripts/migrations/023.js
+++ b/app/scripts/migrations/023.js
@@ -28,23 +28,27 @@ module.exports = {
function transformState (state) {
const newState = state
- const transactions = newState.TransactionController.transactions
-
- if (transactions.length <= 40) return newState
-
- let reverseTxList = transactions.reverse()
- let stripping = true
- while (reverseTxList.length > 40 && stripping) {
- let txIndex = reverseTxList.findIndex((txMeta) => {
- return (txMeta.status === 'failed' ||
- txMeta.status === 'rejected' ||
- txMeta.status === 'confirmed' ||
- txMeta.status === 'dropped')
- })
- if (txIndex < 0) stripping = false
- else reverseTxList.splice(txIndex, 1)
- }
- newState.TransactionController.transactions = reverseTxList.reverse()
+ const { TransactionController } = newState
+ if (TransactionController && TransactionController.transactions) {
+ const transactions = newState.TransactionController.transactions
+
+ if (transactions.length <= 40) return newState
+
+ let reverseTxList = transactions.reverse()
+ let stripping = true
+ while (reverseTxList.length > 40 && stripping) {
+ let txIndex = reverseTxList.findIndex((txMeta) => {
+ return (txMeta.status === 'failed' ||
+ txMeta.status === 'rejected' ||
+ txMeta.status === 'confirmed' ||
+ txMeta.status === 'dropped')
+ })
+ if (txIndex < 0) stripping = false
+ else reverseTxList.splice(txIndex, 1)
+ }
+
+ newState.TransactionController.transactions = reverseTxList.reverse()
+ }
return newState
}
diff --git a/app/scripts/platforms/sw.js b/app/scripts/platforms/sw.js
index 007d8dc5b..56c5f2774 100644
--- a/app/scripts/platforms/sw.js
+++ b/app/scripts/platforms/sw.js
@@ -1,20 +1,25 @@
-
class SwPlatform {
-
- //
- // Public
- //
-
+ /**
+ * Reloads the platform
+ */
reload () {
- // you cant actually do this
- global.location.reload()
+ // TODO: you can't actually do this
+ /** @type {any} */ (global).location.reload()
}
- openWindow ({ url }) {
- // this doesnt actually work
- global.open(url, '_blank')
+ /**
+ * Opens a window
+ * @param {{url: string}} opts - The window options
+ */
+ openWindow (opts) {
+ // TODO: this doesn't actually work
+ /** @type {any} */ (global).open(opts.url, '_blank')
}
+ /**
+ * Returns the platform version
+ * @returns {string}
+ */
getVersion () {
return '<unable to read version>'
}
diff --git a/app/scripts/platforms/window.js b/app/scripts/platforms/window.js
index 1527c008b..943b2a703 100644
--- a/app/scripts/platforms/window.js
+++ b/app/scripts/platforms/window.js
@@ -1,18 +1,23 @@
-
class WindowPlatform {
-
- //
- // Public
- //
-
+ /**
+ * Reload the platform
+ */
reload () {
- global.location.reload()
+ /** @type {any} */ (global).location.reload()
}
- openWindow ({ url }) {
- global.open(url, '_blank')
+ /**
+ * Opens a window
+ * @param {{url: string}} opts - The window options
+ */
+ openWindow (opts) {
+ /** @type {any} */ (global).open(opts.url, '_blank')
}
+ /**
+ * Returns the platform version
+ * @returns {string}
+ */
getVersion () {
return '<unable to read version>'
}
diff --git a/app/scripts/popup-core.js b/app/scripts/popup-core.js
index 2e4334bb1..6325b8a8d 100644
--- a/app/scripts/popup-core.js
+++ b/app/scripts/popup-core.js
@@ -7,10 +7,14 @@ const launchMetamaskUi = require('../../ui')
const StreamProvider = require('web3-stream-provider')
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
-
module.exports = initializePopup
-
+/**
+ * Asynchronously initializes the MetaMask popup UI
+ *
+ * @param {{ container: Element, connectionStream: * }} config Popup configuration object
+ * @param {Function} cb Called when initialization is complete
+ */
function initializePopup ({ container, connectionStream }, cb) {
// setup app
async.waterfall([
@@ -19,6 +23,12 @@ function initializePopup ({ container, connectionStream }, cb) {
], cb)
}
+/**
+ * Establishes streamed connections to background scripts and a Web3 provider
+ *
+ * @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection
+ * @param {Function} cb Called when controller connection is established
+ */
function connectToAccountManager (connectionStream, cb) {
// setup communication with background
// setup multiplexing
@@ -28,6 +38,11 @@ function connectToAccountManager (connectionStream, cb) {
setupWeb3Connection(mx.createStream('provider'))
}
+/**
+ * Establishes a streamed connection to a Web3 provider
+ *
+ * @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection
+ */
function setupWeb3Connection (connectionStream) {
var providerStream = new StreamProvider()
providerStream.pipe(connectionStream).pipe(providerStream)
@@ -38,6 +53,12 @@ function setupWeb3Connection (connectionStream) {
global.eth = new Eth(providerStream)
}
+/**
+ * Establishes a streamed connection to the background account manager
+ *
+ * @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection
+ * @param {Function} cb Called when the remote account manager connection is established
+ */
function setupControllerConnection (connectionStream, cb) {
// this is a really sneaky way of adding EventEmitter api
// to a bi-directional dnode instance
diff --git a/app/scripts/ui.js b/app/scripts/ui.js
index 13c7ac5ec..bdab29c1e 100644
--- a/app/scripts/ui.js
+++ b/app/scripts/ui.js
@@ -3,12 +3,14 @@ const OldMetaMaskUiCss = require('../../old-ui/css')
const NewMetaMaskUiCss = require('../../ui/css')
const startPopup = require('./popup-core')
const PortStream = require('./lib/port-stream.js')
-const isPopupOrNotification = require('./lib/is-popup-or-notification')
+const { getEnvironmentType } = require('./lib/util')
+const { ENVIRONMENT_TYPE_NOTIFICATION } = require('./lib/enums')
const extension = require('extensionizer')
const ExtensionPlatform = require('./platforms/extension')
const NotificationManager = require('./lib/notification-manager')
const notificationManager = new NotificationManager()
const setupRaven = require('./lib/setupRaven')
+const log = require('loglevel')
start().catch(log.error)
@@ -26,7 +28,7 @@ async function start() {
// injectCss(css)
// identify window type (popup, notification)
- const windowType = isPopupOrNotification()
+ const windowType = getEnvironmentType(window.location.href)
global.METAMASK_UI_TYPE = windowType
closePopupIfOpen(windowType)
@@ -68,7 +70,7 @@ async function start() {
function closePopupIfOpen (windowType) {
- if (windowType !== 'notification') {
+ if (windowType !== ENVIRONMENT_TYPE_NOTIFICATION) {
// should close only chrome popup
notificationManager.closePopup()
}