diff --git a/.eslintrc b/.eslintrc
index 8af71bf4e..511d6f216 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -35,7 +35,6 @@
"globals": {
"document": false,
- "log": true,
"navigator": false,
"web3": true,
"window": false
diff --git a/.jshintrc b/.jshintrc
deleted file mode 100644
index 6dc5ecc6b..000000000
--- a/.jshintrc
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "node": true,
- "browser": true,
- "esnext": true,
- "bitwise": true,
- "camelcase": true,
- "curly": true,
- "eqeqeq": true,
- "immed": true,
- "indent": 2,
- "latedef": true,
- "newcap": true,
- "noarg": true,
- "quotmark": "single",
- "regexp": true,
- "undef": true,
- "unused": true,
- "strict": true,
- "trailing": true,
- "smarttabs": true,
- "globals" : {
- "chrome": true,
- "crypto": true,
- "describe": true,
- "it": true
- }
-}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d21abb3bb..9537657ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@
- Correctly format currency conversion for locally selected preferred currency.
- Improved performance of 3D fox logo.
+- Fetch token prices based on contract address, not symbol
+- Fix bug that prevents setting language locale in settings.
## 4.5.5 Fri Apr 06 2018
diff --git a/README.md b/README.md
index 2e68991b5..ca25fc0b9 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
# MetaMask Browser Extension
[![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master) [![Greenkeeper badge](https://badges.greenkeeper.io/MetaMask/metamask-extension.svg)](https://greenkeeper.io/) [![Stories in Ready](https://badge.waffle.io/MetaMask/metamask-extension.png?label=in%20progress&title=waffle.io)](https://waffle.io/MetaMask/metamask-extension)
+[Internal documentation](./docs/jsdocs)
## Support
@@ -68,6 +69,7 @@ To write tests that will be run in the browser using QUnit, add your test files
- [How to develop a live-reloading UI](./docs/ui-dev-mode.md)
- [How to add a new translation to MetaMask](./docs/translating-guide.md)
- [Publishing Guide](./docs/publishing.md)
+- [The MetaMask Team](./docs/team.md)
- [How to develop an in-browser mocked UI](./docs/ui-mock-mode.md)
- [How to live reload on local dependency changes](./docs/developing-on-deps.md)
- [How to add new networks to the Provider Menu](./docs/adding-new-networks.md)
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/index.json b/app/_locales/index.json
index 727504bda..facee3c35 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" },
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 5878cd2e8..6550e8944 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -21,12 +21,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 +48,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 })
@@ -173,7 +177,7 @@ function setupController (initState, initLangCode) {
return versionedData
}
- function persistData(state) {
+ function persistData (state) {
if (!state) {
throw new Error('MetaMask - updated state is missing', state)
}
@@ -192,30 +196,53 @@ 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
+ }
+
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 {
@@ -258,10 +285,11 @@ function setupController (initState, initLangCode) {
// popup trigger
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/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/currency.js b/app/scripts/controllers/currency.js
index 36b8808aa..d9e0a3e34 100644
--- a/app/scripts/controllers/currency.js
+++ b/app/scripts/controllers/currency.js
@@ -1,5 +1,6 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
+const log = require('loglevel')
// every ten minutes
const POLLING_INTERVAL = 600000
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.js b/app/scripts/controllers/network.js
index 617456cd7..45574e673 100644
--- a/app/scripts/controllers/network.js
+++ b/app/scripts/controllers/network.js
@@ -9,6 +9,7 @@ const extend = require('xtend')
const EthQuery = require('eth-query')
const createEventEmitterProxy = require('../lib/events-proxy.js')
const networkConfig = require('../config.js')
+const log = require('loglevel')
const { OLD_UI_NETWORK_TYPE, DEFAULT_RPC } = networkConfig.enums
const INFURA_PROVIDER_TYPES = ['ropsten', 'rinkeby', 'kovan', 'mainnet']
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..b38b3812d 100644
--- a/app/scripts/controllers/shapeshift.js
+++ b/app/scripts/controllers/shapeshift.js
@@ -1,5 +1,6 @@
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
diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js
new file mode 100644
index 000000000..22e3e8154
--- /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://exchanges.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} - Interval used to poll for exchange rates
+ */
+ set interval (interval) {
+ this._handle && clearInterval(this._handle)
+ if (!interval) { return }
+ this._handle = setInterval(() => { this.updateExchangeRates() }, interval)
+ }
+
+ /**
+ * @type {Object} - Preferences controller instance
+ */
+ 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} - Array of token objects with contract addresses
+ */
+ 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/inpage.js b/app/scripts/inpage.js
index ec99bfc35..92c732813 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
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/createLoggerMiddleware.js b/app/scripts/lib/createLoggerMiddleware.js
index 2707cbd9e..fc6abf828 100644
--- a/app/scripts/lib/createLoggerMiddleware.js
+++ b/app/scripts/lib/createLoggerMiddleware.js
@@ -1,3 +1,5 @@
+const log = require('loglevel')
+
// log rpc activity
module.exports = createLoggerMiddleware
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/is-popup-or-notification.js b/app/scripts/lib/is-popup-or-notification.js
deleted file mode 100644
index ad3e825c0..000000000
--- a/app/scripts/lib/is-popup-or-notification.js
+++ /dev/null
@@ -1,12 +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(?:\?.+)*$/) || 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..2dda0ba1f 100644
--- a/app/scripts/lib/local-store.js
+++ b/app/scripts/lib/local-store.js
@@ -3,6 +3,7 @@
// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/storage/local
const extension = require('extensionizer')
+const log = require('loglevel')
module.exports = class ExtensionStore {
constructor() {
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/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/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..df815906f 100644
--- a/app/scripts/lib/util.js
+++ b/app/scripts/lib/util.js
@@ -1,20 +1,27 @@
const ethUtil = require('ethereumjs-util')
const assert = require('assert')
const BN = require('bn.js')
-
-module.exports = {
- getStack,
- sufficientBalance,
- hexToBn,
- bnToHex,
- BnMultiplyByFraction,
-}
+const {
+ ENVIRONMENT_TYPE_POPUP,
+ ENVIRONMENT_TYPE_NOTIFICATION,
+ ENVIRONMENT_TYPE_FULLSCREEN,
+} = require('./enums')
function getStack () {
const stack = new Error('Stack trace generator - not an error').stack
return stack
}
+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
+ }
+}
+
function sufficientBalance (txParams, hexBalance) {
// validate hexBalance is a hex string
assert.equal(typeof hexBalance, 'string', 'sufficientBalance - hexBalance is not a hex string')
@@ -42,3 +49,12 @@ function BnMultiplyByFraction (targetBN, numerator, denominator) {
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..a12b6776e 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))
}
/**
@@ -279,6 +270,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,33 +300,16 @@ 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(),
- }
- )
+ },
+ }
}
/**
@@ -1057,4 +1032,12 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
+ set isClientOpen (open) {
+ this._isClientOpen = open
+ this.isClientOpenAndUnlocked = this.getState().isUnlocked && open
+ }
+
+ set isClientOpenAndUnlocked (active) {
+ this.tokenRatesController.isActive = active
+ }
}
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()
}
diff --git a/development/tools/.jsdoc.json b/development/tools/.jsdoc.json
new file mode 100644
index 000000000..fd90bf89f
--- /dev/null
+++ b/development/tools/.jsdoc.json
@@ -0,0 +1,25 @@
+{
+ "tags": {
+ "allowUnknownTags": false
+ },
+ "source": {
+ "include": "app/scripts/",
+ "includePattern": ".js$",
+ "excludePattern": "(node_modules/|docs)"
+ },
+ "plugins": [
+ "plugins/markdown"
+ ],
+ "opts": {
+ "template": "node_modules/radgrad-jsdoc-template/",
+ "encoding": "utf8",
+ "destination": "docs/jsdocs",
+ "recurse": true,
+ "verbose": true
+ },
+ "templates": {
+ "cleverLinks": false,
+ "monospaceLinks": false
+ }
+}
+
diff --git a/development/tools/README.md b/development/tools/README.md
new file mode 100644
index 000000000..caef51fe6
--- /dev/null
+++ b/development/tools/README.md
@@ -0,0 +1,15 @@
+# Development Tools & Configurations
+
+This folder contains configuration files which are used by the the different
+development-tools, like e.g. JsDoc.
+
+
+## Appveyor
+
+
+https://www.appveyor.com/docs/build-configuration/#alternative-yaml-file-location
+
+Withtin the configuration, point to a weblocation of a txt config file:
+
+https://ci.appveyor.com/project/lazaridiscom/mm-vault/settings
+https://raw.githubusercontent.com/lazaridiscom/mm-vault/master/dev/tools/appveyor.txt
diff --git a/development/tools/appveyor.txt b/development/tools/appveyor.txt
new file mode 100644
index 000000000..4ed974079
--- /dev/null
+++ b/development/tools/appveyor.txt
@@ -0,0 +1,21 @@
+# Test against the latest version of this Node.js version
+environment:
+ nodejs_version: "8"
+
+# Install scripts. (runs after repo cloning)
+install:
+ # Get the latest stable version of Node.js or io.js
+ - ps: Install-Product node $env:nodejs_version
+ # install modules
+ - npm install
+
+# Post-install test scripts.
+test_script:
+ # Output useful info for debugging.
+ - node --version
+ - npm --version
+ # run tests
+ - npm test
+
+# Don't actually build.
+build: off
diff --git a/docs/jsdocs/controllers_transactions.js.html b/docs/jsdocs/controllers_transactions.js.html
new file mode 100644
index 000000000..f86d3aa48
--- /dev/null
+++ b/docs/jsdocs/controllers_transactions.js.html
@@ -0,0 +1,471 @@
+
+
+
+
+
+
+ controllers/transactions.js - Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Classes
+
+
+
+
+
controllers/transactions.js
+
+
+
+
+
+
+
+
+
+ const EventEmitter = require('events')
+const ObservableStore = require('obs-store')
+const ethUtil = require('ethereumjs-util')
+/**
+ * @file The transaction controller. Receives incoming transactions, and emits events for various states of their processing.
+ * @copyright Copyright (c) 2018 MetaMask
+ * @license MIT
+ */
+
+
+const Transaction = require('ethereumjs-tx')
+const EthQuery = require('ethjs-query')
+const TransactionStateManager = require('../lib/tx-state-manager')
+const TxGasUtil = require('../lib/tx-gas-utils')
+const PendingTransactionTracker = require('../lib/pending-tx-tracker')
+const NonceTracker = require('../lib/nonce-tracker')
+
+/*
+ Transaction Controller is an aggregate of sub-controllers and trackers
+ composing them in a way to be exposed to the metamask controller
+ - txStateManager
+ responsible for the state of a transaction and
+ storing the transaction
+ - pendingTxTracker
+ watching blocks for transactions to be include
+ and emitting confirmed events
+ - txGasUtil
+ gas calculations and safety buffering
+ - nonceTracker
+ calculating nonces
+*/
+
+module.exports = class TransactionController extends EventEmitter {
+ constructor (opts) {
+ super()
+ this.networkStore = opts.networkStore || new ObservableStore({})
+ this.preferencesStore = opts.preferencesStore || new ObservableStore({})
+ this.provider = opts.provider
+ this.blockTracker = opts.blockTracker
+ this.signEthTx = opts.signTransaction
+ this.getGasPrice = opts.getGasPrice
+
+ this.memStore = new ObservableStore({})
+ this.query = new EthQuery(this.provider)
+ this.txGasUtil = new TxGasUtil(this.provider)
+
+ this.txStateManager = new TransactionStateManager({
+ initState: opts.initState,
+ txHistoryLimit: opts.txHistoryLimit,
+ getNetwork: this.getNetwork.bind(this),
+ })
+
+ this.txStateManager.getFilteredTxList({
+ status: 'unapproved',
+ loadingDefaults: true,
+ }).forEach((tx) => {
+ this.addTxDefaults(tx)
+ .then((txMeta) => {
+ txMeta.loadingDefaults = false
+ this.txStateManager.updateTx(txMeta, 'transactions: gas estimation for tx on boot')
+ }).catch((error) => {
+ this.txStateManager.setTxStatusFailed(tx.id, error)
+ })
+ })
+
+ this.txStateManager.getFilteredTxList({
+ status: 'approved',
+ }).forEach((txMeta) => {
+ const txSignError = new Error('Transaction found as "approved" during boot - possibly stuck during signing')
+ this.txStateManager.setTxStatusFailed(txMeta.id, txSignError)
+ })
+
+
+ this.store = this.txStateManager.store
+ this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update'))
+ this.nonceTracker = new NonceTracker({
+ provider: this.provider,
+ getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
+ getConfirmedTransactions: (address) => {
+ return this.txStateManager.getFilteredTxList({
+ from: address,
+ status: 'confirmed',
+ err: undefined,
+ })
+ },
+ })
+
+ this.pendingTxTracker = new PendingTransactionTracker({
+ provider: this.provider,
+ nonceTracker: this.nonceTracker,
+ publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx),
+ getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
+ getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
+ })
+
+ this.txStateManager.store.subscribe(() => this.emit('update:badge'))
+
+ this.pendingTxTracker.on('tx:warning', (txMeta) => {
+ this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning')
+ })
+ this.pendingTxTracker.on('tx:confirmed', (txId) => this._markNonceDuplicatesDropped(txId))
+ this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
+ this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
+ if (!txMeta.firstRetryBlockNumber) {
+ txMeta.firstRetryBlockNumber = latestBlockNumber
+ this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:block-update')
+ }
+ })
+ this.pendingTxTracker.on('tx:retry', (txMeta) => {
+ if (!('retryCount' in txMeta)) txMeta.retryCount = 0
+ txMeta.retryCount++
+ this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry')
+ })
+
+ this.blockTracker.on('block', this.pendingTxTracker.checkForTxInBlock.bind(this.pendingTxTracker))
+ // this is a little messy but until ethstore has been either
+ // removed or redone this is to guard against the race condition
+ this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker))
+ this.blockTracker.on('sync', this.pendingTxTracker.queryPendingTxs.bind(this.pendingTxTracker))
+ // memstore is computed from a few different stores
+ this._updateMemstore()
+ this.txStateManager.store.subscribe(() => this._updateMemstore())
+ this.networkStore.subscribe(() => this._updateMemstore())
+ this.preferencesStore.subscribe(() => this._updateMemstore())
+ }
+
+ getState () {
+ return this.memStore.getState()
+ }
+
+ getNetwork () {
+ return this.networkStore.getState()
+ }
+
+ getSelectedAddress () {
+ return this.preferencesStore.getState().selectedAddress
+ }
+
+ getUnapprovedTxCount () {
+ return Object.keys(this.txStateManager.getUnapprovedTxList()).length
+ }
+
+ getPendingTxCount (account) {
+ return this.txStateManager.getPendingTransactions(account).length
+ }
+
+ getFilteredTxList (opts) {
+ return this.txStateManager.getFilteredTxList(opts)
+ }
+
+ getChainId () {
+ const networkState = this.networkStore.getState()
+ const getChainId = parseInt(networkState)
+ if (Number.isNaN(getChainId)) {
+ return 0
+ } else {
+ return getChainId
+ }
+ }
+
+ wipeTransactions (address) {
+ this.txStateManager.wipeTransactions(address)
+ }
+
+ // Adds a tx to the txlist
+ addTx (txMeta) {
+ this.txStateManager.addTx(txMeta)
+ this.emit(`${txMeta.id}:unapproved`, txMeta)
+ }
+
+ async newUnapprovedTransaction (txParams, opts = {}) {
+ log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
+ const initialTxMeta = await this.addUnapprovedTransaction(txParams)
+ initialTxMeta.origin = opts.origin
+ this.txStateManager.updateTx(initialTxMeta, '#newUnapprovedTransaction - adding the origin')
+ // listen for tx completion (success, fail)
+ return new Promise((resolve, reject) => {
+ this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => {
+ switch (finishedTxMeta.status) {
+ case 'submitted':
+ return resolve(finishedTxMeta.hash)
+ case 'rejected':
+ return reject(new Error('MetaMask Tx Signature: User denied transaction signature.'))
+ case 'failed':
+ return reject(new Error(finishedTxMeta.err.message))
+ default:
+ return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))
+ }
+ })
+ })
+ }
+
+ async addUnapprovedTransaction (txParams) {
+ // validate
+ const normalizedTxParams = this._normalizeTxParams(txParams)
+ this._validateTxParams(normalizedTxParams)
+ // construct txMeta
+ let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams })
+ this.addTx(txMeta)
+ this.emit('newUnapprovedTx', txMeta)
+ // add default tx params
+ try {
+ txMeta = await this.addTxDefaults(txMeta)
+ } catch (error) {
+ console.log(error)
+ this.txStateManager.setTxStatusFailed(txMeta.id, error)
+ throw error
+ }
+ txMeta.loadingDefaults = false
+ // save txMeta
+ this.txStateManager.updateTx(txMeta)
+
+ return txMeta
+ }
+
+ async addTxDefaults (txMeta) {
+ const txParams = txMeta.txParams
+ // ensure value
+ txMeta.gasPriceSpecified = Boolean(txParams.gasPrice)
+ let gasPrice = txParams.gasPrice
+ if (!gasPrice) {
+ gasPrice = this.getGasPrice ? this.getGasPrice() : await this.query.gasPrice()
+ }
+ txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16))
+ txParams.value = txParams.value || '0x0'
+ // set gasLimit
+ return await this.txGasUtil.analyzeGasUsage(txMeta)
+ }
+
+ async retryTransaction (originalTxId) {
+ const originalTxMeta = this.txStateManager.getTx(originalTxId)
+ const lastGasPrice = originalTxMeta.txParams.gasPrice
+ const txMeta = this.txStateManager.generateTxMeta({
+ txParams: originalTxMeta.txParams,
+ lastGasPrice,
+ loadingDefaults: false,
+ })
+ this.addTx(txMeta)
+ this.emit('newUnapprovedTx', txMeta)
+ return txMeta
+ }
+
+ async updateTransaction (txMeta) {
+ this.txStateManager.updateTx(txMeta, 'confTx: user updated transaction')
+ }
+
+ async updateAndApproveTransaction (txMeta) {
+ this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
+ await this.approveTransaction(txMeta.id)
+ }
+
+ async approveTransaction (txId) {
+ let nonceLock
+ try {
+ // approve
+ this.txStateManager.setTxStatusApproved(txId)
+ // get next nonce
+ const txMeta = this.txStateManager.getTx(txId)
+ const fromAddress = txMeta.txParams.from
+ // wait for a nonce
+ nonceLock = await this.nonceTracker.getNonceLock(fromAddress)
+ // add nonce to txParams
+ // if txMeta has lastGasPrice then it is a retry at same nonce with higher
+ // gas price transaction and their for the nonce should not be calculated
+ const nonce = txMeta.lastGasPrice ? txMeta.txParams.nonce : nonceLock.nextNonce
+ txMeta.txParams.nonce = ethUtil.addHexPrefix(nonce.toString(16))
+ // add nonce debugging information to txMeta
+ txMeta.nonceDetails = nonceLock.nonceDetails
+ this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction')
+ // sign transaction
+ const rawTx = await this.signTransaction(txId)
+ await this.publishTransaction(txId, rawTx)
+ // must set transaction to submitted/failed before releasing lock
+ nonceLock.releaseLock()
+ } catch (err) {
+ this.txStateManager.setTxStatusFailed(txId, err)
+ // must set transaction to submitted/failed before releasing lock
+ if (nonceLock) nonceLock.releaseLock()
+ // continue with error chain
+ throw err
+ }
+ }
+
+ async signTransaction (txId) {
+ const txMeta = this.txStateManager.getTx(txId)
+ // add network/chain id
+ const chainId = this.getChainId()
+ const txParams = Object.assign({}, txMeta.txParams, { chainId })
+ // sign tx
+ const fromAddress = txParams.from
+ const ethTx = new Transaction(txParams)
+ await this.signEthTx(ethTx, fromAddress)
+ // set state to signed
+ this.txStateManager.setTxStatusSigned(txMeta.id)
+ const rawTx = ethUtil.bufferToHex(ethTx.serialize())
+ return rawTx
+ }
+
+ async publishTransaction (txId, rawTx) {
+ const txMeta = this.txStateManager.getTx(txId)
+ txMeta.rawTx = rawTx
+ this.txStateManager.updateTx(txMeta, 'transactions#publishTransaction')
+ const txHash = await this.query.sendRawTransaction(rawTx)
+ this.setTxHash(txId, txHash)
+ this.txStateManager.setTxStatusSubmitted(txId)
+ }
+
+ async cancelTransaction (txId) {
+ this.txStateManager.setTxStatusRejected(txId)
+ }
+
+ // receives a txHash records the tx as signed
+ setTxHash (txId, txHash) {
+ // Add the tx hash to the persisted meta-tx object
+ const txMeta = this.txStateManager.getTx(txId)
+ txMeta.hash = txHash
+ this.txStateManager.updateTx(txMeta, 'transactions#setTxHash')
+ }
+
+//
+// PRIVATE METHODS
+//
+
+ _normalizeTxParams (txParams) {
+ // functions that handle normalizing of that key in txParams
+ const whiteList = {
+ from: from => ethUtil.addHexPrefix(from).toLowerCase(),
+ to: to => ethUtil.addHexPrefix(txParams.to).toLowerCase(),
+ nonce: nonce => ethUtil.addHexPrefix(nonce),
+ value: value => ethUtil.addHexPrefix(value),
+ data: data => ethUtil.addHexPrefix(data),
+ gas: gas => ethUtil.addHexPrefix(gas),
+ gasPrice: gasPrice => ethUtil.addHexPrefix(gasPrice),
+ }
+
+ // apply only keys in the whiteList
+ const normalizedTxParams = {}
+ Object.keys(whiteList).forEach((key) => {
+ if (txParams[key]) normalizedTxParams[key] = whiteList[key](txParams[key])
+ })
+
+ return normalizedTxParams
+ }
+
+ _validateTxParams (txParams) {
+ this._validateFrom(txParams)
+ this._validateRecipient(txParams)
+ if ('value' in txParams) {
+ const value = txParams.value.toString()
+ if (value.includes('-')) {
+ throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
+ }
+
+ if (value.includes('.')) {
+ throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`)
+ }
+ }
+ }
+
+ _validateFrom (txParams) {
+ if ( !(typeof txParams.from === 'string') ) throw new Error(`Invalid from address ${txParams.from} not a string`)
+ if (!ethUtil.isValidAddress(txParams.from)) throw new Error('Invalid from address')
+ }
+
+ _validateRecipient (txParams) {
+ if (txParams.to === '0x' || txParams.to === null ) {
+ if (txParams.data) {
+ delete txParams.to
+ } else {
+ throw new Error('Invalid recipient address')
+ }
+ } else if ( txParams.to !== undefined && !ethUtil.isValidAddress(txParams.to) ) {
+ throw new Error('Invalid recipient address')
+ }
+ return txParams
+ }
+
+ _markNonceDuplicatesDropped (txId) {
+ this.txStateManager.setTxStatusConfirmed(txId)
+ // get the confirmed transactions nonce and from address
+ const txMeta = this.txStateManager.getTx(txId)
+ const { nonce, from } = txMeta.txParams
+ const sameNonceTxs = this.txStateManager.getFilteredTxList({nonce, from})
+ if (!sameNonceTxs.length) return
+ // mark all same nonce transactions as dropped and give i a replacedBy hash
+ sameNonceTxs.forEach((otherTxMeta) => {
+ if (otherTxMeta.id === txId) return
+ otherTxMeta.replacedBy = txMeta.hash
+ this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce')
+ this.txStateManager.setTxStatusDropped(otherTxMeta.id)
+ })
+ }
+
+ _updateMemstore () {
+ const unapprovedTxs = this.txStateManager.getUnapprovedTxList()
+ const selectedAddressTxList = this.txStateManager.getFilteredTxList({
+ from: this.getSelectedAddress(),
+ metamaskNetworkId: this.getNetwork(),
+ })
+ this.memStore.updateState({ unapprovedTxs, selectedAddressTxList })
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Thu Apr 12 2018 14:37:39 GMT-0700 (PDT) using the radgrad jsdoc theme. Derived from docdash.
+
+
+
+
+
+
+
diff --git a/docs/jsdocs/global.html b/docs/jsdocs/global.html
new file mode 100644
index 000000000..c06006e58
--- /dev/null
+++ b/docs/jsdocs/global.html
@@ -0,0 +1,486 @@
+
+
+
+
+
+
+ Global - Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Classes
+
+
+
+
+
Global
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Type Definitions
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Properties:
+
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ platform
+
+
+
+
+
+Platform
+
+
+
+
+
+
+
+
+
+ An object including platform-specific functions.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Type:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Properties:
+
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ reload
+
+
+
+
+
+function
+
+
+
+
+
+
+
+
+
+ A function to reload the application.
+
+
+
+
+
+
+ openWindow
+
+
+
+
+
+function
+
+
+
+
+
+
+
+
+
+ Opens a URL in the web browser.
+
+
+
+
+
+
+ getVersion
+
+
+
+
+
+function
+
+
+
+
+
+
+
+
+
+ Gets the current version of MetaMask.
+
+
+
+
+
+
+ openExtensionInBrowser
+
+
+
+
+
+function
+
+
+
+
+
+
+
+
+
+ Opens the MetaMask UI in a full window.
+
+
+
+
+
+
+ getPlatformInfo
+
+
+
+
+
+function
+
+
+
+
+
+
+
+
+
+ Callback function that returns info about the current platform.
+
+
+
+
+
+
+
+
+
+
+
+
+
An object that provides a variety of platform-specific functions.
+
+
+
+
+ Type:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Thu Apr 12 2018 14:37:39 GMT-0700 (PDT) using the radgrad jsdoc theme. Derived from docdash.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/jsdocs/index.html b/docs/jsdocs/index.html
new file mode 100644
index 000000000..7ac43e45d
--- /dev/null
+++ b/docs/jsdocs/index.html
@@ -0,0 +1,275 @@
+
+
+
+
+
+
+ Home - Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Classes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ controllers/transactions.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Copyright:
+ Copyright (c) 2018 MetaMask
+
+
+
+ License:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
The transaction controller. Receives incoming transactions, and emits events for various states of their processing.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ metamask-controller.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Copyright:
+ Copyright (c) 2018 MetaMask
+
+
+
+ License:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
The central metamask controller. Aggregates other controllers and exports an api.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Thu Apr 12 2018 14:37:39 GMT-0700 (PDT) using the radgrad jsdoc theme. Derived from docdash.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/jsdocs/metamask-controller.js.html b/docs/jsdocs/metamask-controller.js.html
new file mode 100644
index 000000000..2162138d1
--- /dev/null
+++ b/docs/jsdocs/metamask-controller.js.html
@@ -0,0 +1,1144 @@
+
+
+
+
+
+
+ metamask-controller.js - Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Classes
+
+
+
+
+
metamask-controller.js
+
+
+
+
+
+
+
+
+
+ /**
+ * @file The central metamask controller. Aggregates other controllers and exports an api.
+ * @copyright Copyright (c) 2018 MetaMask
+ * @license MIT
+ */
+
+const EventEmitter = require('events')
+const extend = require('xtend')
+const pump = require('pump')
+const Dnode = require('dnode')
+const ObservableStore = require('obs-store')
+const asStream = require('obs-store/lib/asStream')
+const AccountTracker = require('./lib/account-tracker')
+const RpcEngine = require('json-rpc-engine')
+const debounce = require('debounce')
+const createEngineStream = require('json-rpc-middleware-stream/engineStream')
+const createFilterMiddleware = require('eth-json-rpc-filters')
+const createOriginMiddleware = require('./lib/createOriginMiddleware')
+const createLoggerMiddleware = require('./lib/createLoggerMiddleware')
+const createProviderMiddleware = require('./lib/createProviderMiddleware')
+const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
+const KeyringController = require('eth-keyring-controller')
+const NetworkController = require('./controllers/network')
+const PreferencesController = require('./controllers/preferences')
+const CurrencyController = require('./controllers/currency')
+const NoticeController = require('./notice-controller')
+const ShapeShiftController = require('./controllers/shapeshift')
+const AddressBookController = require('./controllers/address-book')
+const InfuraController = require('./controllers/infura')
+const BlacklistController = require('./controllers/blacklist')
+const RecentBlocksController = require('./controllers/recent-blocks')
+const MessageManager = require('./lib/message-manager')
+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 ConfigManager = require('./lib/config-manager')
+const nodeify = require('./lib/nodeify')
+const accountImporter = require('./account-import-strategies')
+const getBuyEthUrl = require('./lib/buy-eth-url')
+const Mutex = require('await-semaphore').Mutex
+const version = require('../manifest.json').version
+const BN = require('ethereumjs-util').BN
+const GWEI_BN = new BN('1000000000')
+const percentile = require('percentile')
+const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
+
+/**
+ * @typedef {object} MetaMaskOptions
+ * @property {Platform} platform - An object including platform-specific functions.
+ */
+
+module.exports = class MetamaskController extends EventEmitter {
+
+ /**
+ * @constructor
+ * @param {Object} opts
+ */
+ constructor (opts) {
+ super()
+
+ // Avoids warnings when we use lots of emitters.
+ this.defaultMaxListeners = 20
+
+ this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200)
+ this.opts = opts
+ const initState = opts.initState || {}
+ this.recordFirstTimeInfo(initState)
+
+ // platform-specific api
+ this.platform = opts.platform
+
+ // observable state store
+ this.store = new ObservableStore(initState)
+
+ // lock to ensure only one vault created at once
+ this.createVaultMutex = new Mutex()
+
+ // network store
+ this.networkController = new NetworkController(initState.NetworkController)
+
+ // config manager
+ this.configManager = new ConfigManager({
+ store: this.store,
+ })
+
+ // preferences controller
+ this.preferencesController = new PreferencesController({
+ initState: initState.PreferencesController,
+ initLangCode: opts.initLangCode,
+ })
+
+ // currency controller
+ this.currencyController = new CurrencyController({
+ initState: initState.CurrencyController,
+ })
+ this.currencyController.updateConversionRate()
+ this.currencyController.scheduleConversionInterval()
+
+ // infura controller
+ this.infuraController = new InfuraController({
+ initState: initState.InfuraController,
+ })
+ this.infuraController.scheduleInfuraNetworkCheck()
+
+ this.blacklistController = new BlacklistController()
+ this.blacklistController.scheduleUpdates()
+
+ // rpc provider
+ this.provider = this.initializeProvider()
+ this.blockTracker = this.provider._blockTracker
+
+ this.recentBlocksController = new RecentBlocksController({
+ blockTracker: this.blockTracker,
+ provider: this.provider,
+ })
+
+ // account tracker watches balances, nonces, and any code at their address.
+ this.accountTracker = new AccountTracker({
+ provider: this.provider,
+ blockTracker: this.blockTracker,
+ })
+
+ // key mgmt
+ this.keyringController = new KeyringController({
+ initState: initState.KeyringController,
+ getNetwork: this.networkController.getNetworkState.bind(this.networkController),
+ encryptor: opts.encryptor || undefined,
+ })
+
+ // If only one account exists, make sure it is selected.
+ this.keyringController.memStore.subscribe((state) => {
+ const addresses = state.keyrings.reduce((res, keyring) => {
+ return res.concat(keyring.accounts)
+ }, [])
+ if (addresses.length === 1) {
+ const address = addresses[0]
+ this.preferencesController.setSelectedAddress(address)
+ }
+ this.accountTracker.syncWithAddresses(addresses)
+ })
+
+ // address book controller
+ this.addressBookController = new AddressBookController({
+ initState: initState.AddressBookController,
+ }, this.keyringController)
+
+ // tx mgmt
+ this.txController = new TransactionController({
+ initState: initState.TransactionController || initState.TransactionManager,
+ networkStore: this.networkController.networkStore,
+ preferencesStore: this.preferencesController.store,
+ txHistoryLimit: 40,
+ getNetwork: this.networkController.getNetworkState.bind(this),
+ signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
+ provider: this.provider,
+ blockTracker: this.blockTracker,
+ getGasPrice: this.getGasPrice.bind(this),
+ })
+ this.txController.on('newUnapprovedTx', opts.showUnapprovedTx.bind(opts))
+
+ // computed balances (accounting for pending transactions)
+ this.balancesController = new BalancesController({
+ accountTracker: this.accountTracker,
+ txController: this.txController,
+ blockTracker: this.blockTracker,
+ })
+ this.networkController.on('networkDidChange', () => {
+ this.balancesController.updateAllBalances()
+ })
+ this.balancesController.updateAllBalances()
+
+ // notices
+ this.noticeController = new NoticeController({
+ initState: initState.NoticeController,
+ version,
+ firstVersion: initState.firstTimeInfo.version,
+ })
+ this.noticeController.updateNoticesList()
+ // to be uncommented when retrieving notices from a remote server.
+ // this.noticeController.startPolling()
+
+ this.shapeshiftController = new ShapeShiftController({
+ initState: initState.ShapeShiftController,
+ })
+
+ this.networkController.lookupNetwork()
+ this.messageManager = new MessageManager()
+ this.personalMessageManager = new PersonalMessageManager()
+ 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.infuraController.store.subscribe((state) => {
+ this.store.updateState({ InfuraController: state })
+ })
+
+ // 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)
+ }
+
+ /**
+ * Constructor helper: initialize a provider.
+ */
+ initializeProvider () {
+ const providerOpts = {
+ static: {
+ eth_syncing: false,
+ web3_clientVersion: `MetaMask/v${version}`,
+ eth_sendTransaction: (payload, next, end) => {
+ const origin = payload.origin
+ const txParams = payload.params[0]
+ nodeify(this.txController.newUnapprovedTransaction, this.txController)(txParams, { origin }, end)
+ },
+ },
+ // account mgmt
+ getAccounts: (cb) => {
+ const isUnlocked = this.keyringController.memStore.getState().isUnlocked
+ const result = []
+ const selectedAddress = this.preferencesController.getSelectedAddress()
+
+ // only show address if account is unlocked
+ if (isUnlocked && selectedAddress) {
+ result.push(selectedAddress)
+ }
+ cb(null, result)
+ },
+ // tx signing
+ // old style msg signing
+ processMessage: this.newUnsignedMessage.bind(this),
+ // personal_sign msg signing
+ processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
+ processTypedMessage: this.newUnsignedTypedMessage.bind(this),
+ }
+ const providerProxy = this.networkController.initializeProvider(providerOpts)
+ return providerProxy
+ }
+
+ /**
+ * Constructor helper: initialize a public config store.
+ */
+ initPublicConfigStore () {
+ // get init state
+ const publicConfigStore = new ObservableStore()
+
+ // memStore -> transform -> publicConfigStore
+ this.on('update', (memState) => {
+ const publicState = selectPublicState(memState)
+ publicConfigStore.putState(publicState)
+ })
+
+ function selectPublicState (memState) {
+ const result = {
+ selectedAddress: memState.isUnlocked ? memState.selectedAddress : undefined,
+ networkVersion: memState.network,
+ }
+ return result
+ }
+
+ return publicConfigStore
+ }
+
+//=============================================================================
+// EXPOSED TO THE UI SUBSYSTEM
+//=============================================================================
+
+ /**
+ * The metamask-state of the various controllers, made available to the UI
+ *
+ * @returns {Object} status
+ */
+ getState () {
+ const wallet = this.configManager.getWallet()
+ const vault = this.keyringController.store.getState().vault
+ 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(),
+ {
+ lostAccounts: this.configManager.getLostAccounts(),
+ seedWords: this.configManager.getSeedWords(),
+ forgottenPassword: this.configManager.getPasswordForgotten(),
+ }
+ )
+ }
+
+ /**
+ * Returns an api-object which is consumed by the UI
+ *
+ * @returns {Object}
+ */
+ getApi () {
+ const keyringController = this.keyringController
+ const preferencesController = this.preferencesController
+ const txController = this.txController
+ const noticeController = this.noticeController
+ const addressBookController = this.addressBookController
+ const networkController = this.networkController
+
+ return {
+ // etc
+ getState: (cb) => cb(null, this.getState()),
+ setCurrentCurrency: this.setCurrentCurrency.bind(this),
+ setUseBlockie: this.setUseBlockie.bind(this),
+ setCurrentLocale: this.setCurrentLocale.bind(this),
+ markAccountsFound: this.markAccountsFound.bind(this),
+ markPasswordForgotten: this.markPasswordForgotten.bind(this),
+ unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this),
+
+ // coinbase
+ buyEth: this.buyEth.bind(this),
+ // shapeshift
+ createShapeShiftTx: this.createShapeShiftTx.bind(this),
+
+ // primary HD keyring management
+ addNewAccount: nodeify(this.addNewAccount, this),
+ placeSeedWords: this.placeSeedWords.bind(this),
+ verifySeedPhrase: nodeify(this.verifySeedPhrase, this),
+ clearSeedWordCache: this.clearSeedWordCache.bind(this),
+ resetAccount: nodeify(this.resetAccount, this),
+ importAccountWithStrategy: this.importAccountWithStrategy.bind(this),
+
+ // vault management
+ submitPassword: nodeify(keyringController.submitPassword, keyringController),
+
+ // network management
+ setNetworkEndpoints: nodeify(networkController.setNetworkEndpoints, networkController),
+ setProviderType: nodeify(networkController.setProviderType, networkController),
+ setCustomRpc: nodeify(this.setCustomRpc, this),
+
+ // PreferencesController
+ setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController),
+ addToken: nodeify(preferencesController.addToken, preferencesController),
+ removeToken: nodeify(preferencesController.removeToken, preferencesController),
+ setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
+ setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
+
+ // AddressController
+ setAddressBook: nodeify(addressBookController.setAddressBook, addressBookController),
+
+ // KeyringController
+ setLocked: nodeify(keyringController.setLocked, keyringController),
+ createNewVaultAndKeychain: nodeify(this.createNewVaultAndKeychain, this),
+ createNewVaultAndRestore: nodeify(this.createNewVaultAndRestore, this),
+ addNewKeyring: nodeify(keyringController.addNewKeyring, keyringController),
+ saveAccountLabel: nodeify(keyringController.saveAccountLabel, keyringController),
+ exportAccount: nodeify(keyringController.exportAccount, keyringController),
+
+ // txController
+ cancelTransaction: nodeify(txController.cancelTransaction, txController),
+ updateTransaction: nodeify(txController.updateTransaction, txController),
+ updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
+ retryTransaction: nodeify(this.retryTransaction, this),
+
+ // messageManager
+ signMessage: nodeify(this.signMessage, this),
+ cancelMessage: this.cancelMessage.bind(this),
+
+ // personalMessageManager
+ signPersonalMessage: nodeify(this.signPersonalMessage, this),
+ cancelPersonalMessage: this.cancelPersonalMessage.bind(this),
+
+ // personalMessageManager
+ signTypedMessage: nodeify(this.signTypedMessage, this),
+ cancelTypedMessage: this.cancelTypedMessage.bind(this),
+
+ // notices
+ checkNotices: noticeController.updateNoticesList.bind(noticeController),
+ markNoticeRead: noticeController.markNoticeRead.bind(noticeController),
+ }
+ }
+
+
+
+//=============================================================================
+// VAULT / KEYRING RELATED METHODS
+//=============================================================================
+
+ /**
+ * Creates a new Vault(?) and create a new keychain(?)
+ *
+ * A vault is ...
+ *
+ * A keychain is ...
+ *
+ *
+ * @param {} password
+ *
+ * @returns {} vault
+ */
+ async createNewVaultAndKeychain (password) {
+ const release = await this.createVaultMutex.acquire()
+ let vault
+
+ try {
+ const accounts = await this.keyringController.getAccounts()
+
+ if (accounts.length > 0) {
+ vault = await this.keyringController.fullUpdate()
+
+ } else {
+ vault = await this.keyringController.createNewVaultAndKeychain(password)
+ this.selectFirstIdentity(vault)
+ }
+ release()
+ } catch (err) {
+ release()
+ throw err
+ }
+
+ return vault
+ }
+
+ /**
+ * Create a new Vault and restore an existent keychain
+ * @param {} password
+ * @param {} seed
+ */
+ async createNewVaultAndRestore (password, seed) {
+ const release = await this.createVaultMutex.acquire()
+ try {
+ const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
+ this.selectFirstIdentity(vault)
+ release()
+ return vault
+ } catch (err) {
+ release()
+ throw err
+ }
+ }
+
+ /**
+ * Retrieves the first Identiy from the passed Vault and selects the related address
+ *
+ * An Identity is ...
+ *
+ * @param {} vault
+ */
+ selectFirstIdentity (vault) {
+ const { identities } = vault
+ const address = Object.keys(identities)[0]
+ this.preferencesController.setSelectedAddress(address)
+ }
+
+ // ?
+ // Opinionated Keyring Management
+ //
+
+ /**
+ * Adds a new account to ...
+ *
+ * @returns {} keyState
+ */
+ async addNewAccount () {
+ const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
+ if (!primaryKeyring) {
+ throw new Error('MetamaskController - No HD Key Tree found')
+ }
+ const keyringController = this.keyringController
+ const oldAccounts = await keyringController.getAccounts()
+ const keyState = await keyringController.addNewAccount(primaryKeyring)
+ const newAccounts = await keyringController.getAccounts()
+
+ await this.verifySeedPhrase()
+
+ newAccounts.forEach((address) => {
+ if (!oldAccounts.includes(address)) {
+ this.preferencesController.setSelectedAddress(address)
+ }
+ })
+
+ return keyState
+ }
+
+ /**
+ * Adds the current vault's seed words to the UI's state tree.
+ *
+ * Used when creating a first vault, to allow confirmation.
+ * Also used when revealing the seed words in the confirmation view.
+ */
+ placeSeedWords (cb) {
+
+ this.verifySeedPhrase()
+ .then((seedWords) => {
+ this.configManager.setSeedWords(seedWords)
+ return cb(null, seedWords)
+ })
+ .catch((err) => {
+ return cb(err)
+ })
+ }
+
+ /**
+ * Verifies the validity of the current vault's seed phrase.
+ *
+ * Validity: seed phrase restores the accounts belonging to the current vault.
+ *
+ * Called when the first account is created and on unlocking the vault.
+ */
+ async verifySeedPhrase () {
+
+ const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
+ if (!primaryKeyring) {
+ throw new Error('MetamaskController - No HD Key Tree found')
+ }
+
+ const serialized = await primaryKeyring.serialize()
+ const seedWords = serialized.mnemonic
+
+ const accounts = await primaryKeyring.getAccounts()
+ if (accounts.length < 1) {
+ throw new Error('MetamaskController - No accounts found')
+ }
+
+ try {
+ await seedPhraseVerifier.verifyAccounts(accounts, seedWords)
+ return seedWords
+ } catch (err) {
+ log.error(err.message)
+ throw err
+ }
+ }
+
+ /**
+ * Remove the primary account seed phrase from the UI's state tree.
+ *
+ * The seed phrase remains available in the background process.
+ *
+ */
+ clearSeedWordCache (cb) {
+ this.configManager.setSeedWords(null)
+ cb(null, this.preferencesController.getSelectedAddress())
+ }
+
+ /**
+ * ?
+ */
+ async resetAccount (cb) {
+ const selectedAddress = this.preferencesController.getSelectedAddress()
+ this.txController.wipeTransactions(selectedAddress)
+
+ const networkController = this.networkController
+ const oldType = networkController.getProviderConfig().type
+ await networkController.setProviderType(oldType, true)
+
+ return selectedAddress
+ }
+
+ /**
+ * Imports an account ... ?
+ *
+ * @param {} strategy
+ * @param {} args
+ * @param {} cb
+ */
+ importAccountWithStrategy (strategy, args, cb) {
+ accountImporter.importAccount(strategy, args)
+ .then((privateKey) => {
+ return this.keyringController.addNewKeyring('Simple Key Pair', [ privateKey ])
+ })
+ .then(keyring => keyring.getAccounts())
+ .then((accounts) => this.preferencesController.setSelectedAddress(accounts[0]))
+ .then(() => { cb(null, this.keyringController.fullUpdate()) })
+ .catch((reason) => { cb(reason) })
+ }
+
+ // ---------------------------------------------------------------------------
+ // Identity Management (sign)
+
+ /**
+ * @param {} msgParams
+ * @param {} cb
+ */
+ signMessage (msgParams, cb) {
+ log.info('MetaMaskController - signMessage')
+ const msgId = msgParams.metamaskId
+
+ // sets the status op the message to 'approved'
+ // and removes the metamaskId for signing
+ return this.messageManager.approveMessage(msgParams)
+ .then((cleanMsgParams) => {
+ // signs the message
+ return this.keyringController.signMessage(cleanMsgParams)
+ })
+ .then((rawSig) => {
+ // tells the listener that the message has been signed
+ // and can be returned to the dapp
+ this.messageManager.setMsgStatusSigned(msgId, rawSig)
+ return this.getState()
+ })
+ }
+
+ // Prefixed Style Message Signing Methods:
+
+ /**
+ *
+ * @param {} msgParams
+ * @param {} cb
+ */
+ approvePersonalMessage (msgParams, cb) {
+ const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
+ this.sendUpdate()
+ this.opts.showUnconfirmedMessage()
+ this.personalMessageManager.once(`${msgId}:finished`, (data) => {
+ switch (data.status) {
+ case 'signed':
+ return cb(null, data.rawSig)
+ case 'rejected':
+ return cb(new Error('MetaMask Message Signature: User denied transaction signature.'))
+ default:
+ return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
+ }
+ })
+ }
+
+ /**
+ * @param {} msgParams
+ */
+ signPersonalMessage (msgParams) {
+ log.info('MetaMaskController - signPersonalMessage')
+ const msgId = msgParams.metamaskId
+ // sets the status op the message to 'approved'
+ // and removes the metamaskId for signing
+ return this.personalMessageManager.approveMessage(msgParams)
+ .then((cleanMsgParams) => {
+ // signs the message
+ return this.keyringController.signPersonalMessage(cleanMsgParams)
+ })
+ .then((rawSig) => {
+ // tells the listener that the message has been signed
+ // and can be returned to the dapp
+ this.personalMessageManager.setMsgStatusSigned(msgId, rawSig)
+ return this.getState()
+ })
+ }
+
+ /**
+ * @param {} msgParams
+ */
+ signTypedMessage (msgParams) {
+ log.info('MetaMaskController - signTypedMessage')
+ const msgId = msgParams.metamaskId
+ // sets the status op the message to 'approved'
+ // and removes the metamaskId for signing
+ return this.typedMessageManager.approveMessage(msgParams)
+ .then((cleanMsgParams) => {
+ // signs the message
+ return this.keyringController.signTypedMessage(cleanMsgParams)
+ })
+ .then((rawSig) => {
+ // tells the listener that the message has been signed
+ // and can be returned to the dapp
+ this.typedMessageManager.setMsgStatusSigned(msgId, rawSig)
+ return this.getState()
+ })
+ }
+
+ // ---------------------------------------------------------------------------
+ // Account Restauration
+
+ /**
+ * ?
+ *
+ * @param {} migratorOutput
+ */
+ restoreOldVaultAccounts (migratorOutput) {
+ const { serialized } = migratorOutput
+ return this.keyringController.restoreKeyring(serialized)
+ .then(() => migratorOutput)
+ }
+
+ /**
+ * ?
+ *
+ * @param {} migratorOutput
+ */
+ restoreOldLostAccounts (migratorOutput) {
+ const { lostAccounts } = migratorOutput
+ if (lostAccounts) {
+ this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address))
+ return this.importLostAccounts(migratorOutput)
+ }
+ return Promise.resolve(migratorOutput)
+ }
+
+ /**
+ * Import (lost) Accounts
+ *
+ * @param {Object} {lostAccounts} @Array accounts <{ address, privateKey }>
+ *
+ * Uses the array's private keys to create a new Simple Key Pair keychain
+ * and add it to the keyring controller.
+ */
+ importLostAccounts ({ lostAccounts }) {
+ const privKeys = lostAccounts.map(acct => acct.privateKey)
+ return this.keyringController.restoreKeyring({
+ type: 'Simple Key Pair',
+ data: privKeys,
+ })
+ }
+
+//=============================================================================
+// END (VAULT / KEYRING RELATED METHODS)
+//=============================================================================
+
+//
+
+//=============================================================================
+// MESSAGES
+//=============================================================================
+
+ async retryTransaction (txId, cb) {
+ await this.txController.retryTransaction(txId)
+ const state = await this.getState()
+ return state
+ }
+
+
+ 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())
+ }
+
+ markPasswordForgotten(cb) {
+ this.configManager.setPasswordForgotten(true)
+ this.sendUpdate()
+ cb()
+ }
+
+ unMarkPasswordForgotten(cb) {
+ this.configManager.setPasswordForgotten(false)
+ this.sendUpdate()
+ cb()
+ }
+
+//=============================================================================
+// SETUP
+//=============================================================================
+
+ setupUntrustedCommunication (connectionStream, originDomain) {
+ // Check if new connection is blacklisted
+ if (this.blacklistController.checkForPhishing(originDomain)) {
+ log.debug('MetaMask - sending phishing warning for', originDomain)
+ this.sendPhishingWarning(connectionStream, originDomain)
+ return
+ }
+
+ // setup multiplexing
+ const mux = setupMultiplex(connectionStream)
+ // connect features
+ this.setupProviderConnection(mux.createStream('provider'), originDomain)
+ this.setupPublicConfig(mux.createStream('publicConfig'))
+ }
+
+ setupTrustedCommunication (connectionStream, originDomain) {
+ // setup multiplexing
+ const mux = setupMultiplex(connectionStream)
+ // connect features
+ this.setupControllerConnection(mux.createStream('controller'))
+ this.setupProviderConnection(mux.createStream('provider'), originDomain)
+ }
+
+ sendPhishingWarning (connectionStream, hostname) {
+ const mux = setupMultiplex(connectionStream)
+ const phishingStream = mux.createStream('phishing')
+ phishingStream.write({ hostname })
+ }
+
+ setupControllerConnection (outStream) {
+ const api = this.getApi()
+ const dnode = Dnode(api)
+ pump(
+ outStream,
+ dnode,
+ outStream,
+ (err) => {
+ if (err) log.error(err)
+ }
+ )
+ dnode.on('remote', (remote) => {
+ // push updates to popup
+ const sendUpdate = remote.sendUpdate.bind(remote)
+ this.on('update', sendUpdate)
+ })
+ }
+
+ setupProviderConnection (outStream, origin) {
+ // setup json rpc engine stack
+ const engine = new RpcEngine()
+
+ // create filter polyfill middleware
+ const filterMiddleware = createFilterMiddleware({
+ provider: this.provider,
+ blockTracker: this.provider._blockTracker,
+ })
+
+ engine.push(createOriginMiddleware({ origin }))
+ engine.push(createLoggerMiddleware({ origin }))
+ engine.push(filterMiddleware)
+ engine.push(createProviderMiddleware({ provider: this.provider }))
+
+ // setup connection
+ const providerStream = createEngineStream({ engine })
+ pump(
+ outStream,
+ providerStream,
+ outStream,
+ (err) => {
+ // cleanup filter polyfill middleware
+ filterMiddleware.destroy()
+ if (err) log.error(err)
+ }
+ )
+ }
+
+ setupPublicConfig (outStream) {
+ pump(
+ asStream(this.publicConfigStore),
+ outStream,
+ (err) => {
+ if (err) log.error(err)
+ }
+ )
+ }
+
+ privateSendUpdate () {
+ this.emit('update', this.getState())
+ }
+
+ getGasPrice () {
+ const { recentBlocksController } = this
+ const { recentBlocks } = recentBlocksController.store.getState()
+
+ // Return 1 gwei if no blocks have been observed:
+ if (recentBlocks.length === 0) {
+ return '0x' + GWEI_BN.toString(16)
+ }
+
+ const lowestPrices = recentBlocks.map((block) => {
+ if (!block.gasPrices || block.gasPrices.length < 1) {
+ return GWEI_BN
+ }
+ return block.gasPrices
+ .map(hexPrefix => hexPrefix.substr(2))
+ .map(hex => new BN(hex, 16))
+ .sort((a, b) => {
+ return a.gt(b) ? 1 : -1
+ })[0]
+ })
+ .map(number => number.div(GWEI_BN).toNumber())
+
+ const percentileNum = percentile(50, lowestPrices)
+ const percentileNumBn = new BN(percentileNum)
+ return '0x' + percentileNumBn.mul(GWEI_BN).toString(16)
+ }
+
+//=============================================================================
+// CONFIG
+//=============================================================================
+
+ // Log blocks
+
+ setCurrentCurrency (currencyCode, cb) {
+ try {
+ this.currencyController.setCurrentCurrency(currencyCode)
+ this.currencyController.updateConversionRate()
+ const data = {
+ conversionRate: this.currencyController.getConversionRate(),
+ currentCurrency: this.currencyController.getCurrentCurrency(),
+ conversionDate: this.currencyController.getConversionDate(),
+ }
+ cb(null, data)
+ } catch (err) {
+ cb(err)
+ }
+ }
+
+ buyEth (address, amount) {
+ if (!amount) amount = '5'
+ const network = this.networkController.getNetworkState()
+ const url = getBuyEthUrl({ network, address, amount })
+ if (url) this.platform.openWindow({ url })
+ }
+
+ createShapeShiftTx (depositAddress, depositType) {
+ this.shapeshiftController.createShapeShiftTx(depositAddress, depositType)
+ }
+
+ // network
+
+ async setCustomRpc (rpcTarget, rpcList) {
+ this.networkController.setRpcTarget(rpcTarget)
+ await this.preferencesController.updateFrequentRpcList(rpcTarget)
+ return rpcTarget
+ }
+
+ setUseBlockie (val, cb) {
+ try {
+ this.preferencesController.setUseBlockie(val)
+ cb(null)
+ } catch (err) {
+ cb(err)
+ }
+ }
+
+ setCurrentLocale (key, cb) {
+ try {
+ this.preferencesController.setCurrentLocale(key)
+ cb(null)
+ } catch (err) {
+ cb(err)
+ }
+ }
+
+ /**
+ * Records the MetaMask version and time of first installation,
+ * mutating the initState param.
+ *
+ * @private
+ *
+ * @param {object} initState The initial state passed to the controller,
+ * which may be new.
+ */
+ recordFirstTimeInfo (initState) {
+ if (!('firstTimeInfo' in initState)) {
+ initState.firstTimeInfo = {
+ version,
+ date: Date.now(),
+ }
+ }
+ }
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Thu Apr 12 2018 14:37:39 GMT-0700 (PDT) using the radgrad jsdoc theme. Derived from docdash.
+
+
+
+
+
+
+
diff --git a/docs/jsdocs/module.exports_module.exports.html b/docs/jsdocs/module.exports_module.exports.html
new file mode 100644
index 000000000..5558cea07
--- /dev/null
+++ b/docs/jsdocs/module.exports_module.exports.html
@@ -0,0 +1,229 @@
+
+
+
+
+
+
+ exports - Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Classes
+
+
+
+
+
exports
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
new exports(opts)
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ opts
+
+
+
+
+
+Object
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Thu Apr 12 2018 14:37:39 GMT-0700 (PDT) using the radgrad jsdoc theme. Derived from docdash.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/jsdocs/platforms_extension.js.html b/docs/jsdocs/platforms_extension.js.html
new file mode 100644
index 000000000..c75f6c108
--- /dev/null
+++ b/docs/jsdocs/platforms_extension.js.html
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+ platforms/extension.js - Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Classes
+
+
+
+
+
platforms/extension.js
+
+
+
+
+
+
+
+
+
+ const extension = require('extensionizer')
+
+/**
+ * An object that provides a variety of platform-specific functions.
+ *
+ * @typedef {object} Platform
+ *
+ * @property {Function} reload - A function to reload the application.
+ * @property {Function} openWindow - Opens a URL in the web browser.
+ * @property {Function} getVersion - Gets the current version of MetaMask.
+ * @property {Function} openExtensionInBrowser - Opens the MetaMask UI in a full window.
+ * @property {Function} getPlatformInfo - Callback function that returns info about the current platform.
+ */
+
+class ExtensionPlatform {
+
+ //
+ // Public
+ //
+ reload () {
+ extension.runtime.reload()
+ }
+
+ openWindow ({ url }) {
+ extension.tabs.create({ url })
+ }
+
+ getVersion () {
+ return extension.runtime.getManifest().version
+ }
+
+ openExtensionInBrowser () {
+ const extensionURL = extension.runtime.getURL('home.html')
+ this.openWindow({ url: extensionURL })
+ }
+
+ getPlatformInfo (cb) {
+ try {
+ extension.runtime.getPlatformInfo((platform) => {
+ cb(null, platform)
+ })
+ } catch (e) {
+ cb(e)
+ }
+ }
+}
+
+module.exports = ExtensionPlatform
+
+
+
+
+
+
+
+
+
+
+
+
+ Documentation generated by JSDoc 3.5.5 on Thu Apr 12 2018 14:37:39 GMT-0700 (PDT) using the radgrad jsdoc theme. Derived from docdash.
+
+
+
+
+
+
+
diff --git a/docs/jsdocs/scripts/linenumber.js b/docs/jsdocs/scripts/linenumber.js
new file mode 100644
index 000000000..9cb891484
--- /dev/null
+++ b/docs/jsdocs/scripts/linenumber.js
@@ -0,0 +1,25 @@
+/*global document */
+(function() {
+ var source = document.getElementsByClassName('prettyprint source linenums');
+ var i = 0;
+ var lineNumber = 0;
+ var lineId;
+ var lines;
+ var totalLines;
+ var anchorHash;
+
+ if (source && source[0]) {
+ anchorHash = document.location.hash.substring(1);
+ lines = source[0].getElementsByTagName('li');
+ totalLines = lines.length;
+
+ for (; i < totalLines; i++) {
+ lineNumber++;
+ lineId = 'line' + lineNumber;
+ lines[i].id = lineId;
+ if (lineId === anchorHash) {
+ lines[i].className += ' selected';
+ }
+ }
+ }
+})();
diff --git a/docs/jsdocs/scripts/prettify/Apache-License-2.0.txt b/docs/jsdocs/scripts/prettify/Apache-License-2.0.txt
new file mode 100644
index 000000000..75b52484e
--- /dev/null
+++ b/docs/jsdocs/scripts/prettify/Apache-License-2.0.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/docs/jsdocs/scripts/prettify/lang-css.js b/docs/jsdocs/scripts/prettify/lang-css.js
new file mode 100644
index 000000000..bb6dbea43
--- /dev/null
+++ b/docs/jsdocs/scripts/prettify/lang-css.js
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n"]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com",
+/^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]);
diff --git a/docs/jsdocs/scripts/prettify/prettify.js b/docs/jsdocs/scripts/prettify/prettify.js
new file mode 100644
index 000000000..ec2a488bd
--- /dev/null
+++ b/docs/jsdocs/scripts/prettify/prettify.js
@@ -0,0 +1,28 @@
+var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
+(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
+[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
+l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
+q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
+q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
+"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
+a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
+for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
+"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
+H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
+J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
+I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]+/],["dec",/^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^