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 45eaa6908..3e2b481de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Current Master +- 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. +- Show checksum addresses throughout the UI + ## 4.5.5 Fri Apr 06 2018 - Graceful handling of unknown keys in txParams 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/en/messages.json b/app/_locales/en/messages.json index b372326ee..3b20ab49a 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -908,5 +908,8 @@ }, "youSign": { "message": "You are signing" + }, + "generatingTransaction": { + "message": "Generating transaction" } } diff --git a/app/_locales/hn/messages.json b/app/_locales/hn/messages.json index 323f4b4b3..b869560e5 100644 --- a/app/_locales/hn/messages.json +++ b/app/_locales/hn/messages.json @@ -9,7 +9,7 @@ "message": "खाता विवरण" }, "accountName": { - "message": "खाता का नाम" + "message": "खाते का नाम" }, "address": { "message": "खाते का पता" @@ -21,7 +21,7 @@ "message": "टोकन जोड़ें" }, "addTokens": { - "message": "टोकनो को जोड़ें" + "message": "टोकनों को जोड़ें" }, "amount": { "message": "राशि" @@ -30,7 +30,7 @@ "message": "राशि + गैस" }, "appDescription": { - "message": "एथरेम ब्राउज़र एक्सटेंशन", + "message": "इथीरियम ब्राउज़र एक्सटेंशन", "description": "आवेदन का विवरण" }, "appName": { @@ -53,7 +53,7 @@ "message": "उपलब्ध बैलेंस।" }, "balances": { - "message": "ापके उपलब्ध बैलेंस" + "message": "आपके उपलब्ध बैलेंस" }, "balanceIsInsufficientGas": { "message": "वर्तमान गैस कुल के लिए अपर्याप्त शेष" @@ -78,10 +78,10 @@ "message": "खरीदें" }, "buyCoinbase": { - "message": "कॉनबेस पर खरीदें" + "message": "कॉइनबेस पर खरीदें" }, "buyCoinbaseExplainer": { - "message": "बिल्टकोइन, एथरेम और लाइटकोइन खरीदने और बेचने के लिए दुनिया का सबसे लोकप्रिय तरीका Coinbase है।" + "message": "बिल्टकोइन, इथीरियम और लाइटकोइन खरीदने और बेचने के लिए दुनिया का सबसे लोकप्रिय तरीका कॉइनबेस है।" }, "cancel": { "message": "रद्द करें" @@ -108,7 +108,7 @@ "message": "जारी रखें" }, "continueToCoinbase": { - "message": "कॉ्ोनबेस को ब्हेजना जारी रखें" + "message": "कॉइनबेस को ब्हेजना जारी रखें" }, "contractDeployment": { "message": "अनुबंध परिनियोजन व तैनाती" @@ -435,13 +435,13 @@ "message": "बीज शब्द में केवल लोअरकेस वर्ण होते हैं" }, "mainnet": { - "message": "मुख्य ईथरम नेटवर्क" + "message": "मुख्य इथीरियम नेटवर्क" }, "message": { "message": "संदेश" }, "metamaskDescription": { - "message": "मेटामास्क एथर्मम के लिए एक सुरक्षित पहचान वॉल्ट है।" + "message": "मेटामास्क इथीरियम के लिए एक सुरक्षित पहचान वॉल्ट है।" }, "min": { "message": "न्यूनतम" @@ -649,7 +649,7 @@ "message": "भेजें टोकन" }, "sendTokensAnywhere": { - "message": "इटोरम खाते वाले किसी को भी टोकन भेजें" + "message": "इथीरियम खाते वाले किसी को भी टोकन भेजें" }, "settings": { "message": "सेटिंग्स" diff --git a/app/_locales/index.json b/app/_locales/index.json index c085deb72..7717502b7 100644 --- a/app/_locales/index.json +++ b/app/_locales/index.json @@ -1,4 +1,5 @@ [ + { "code": "cs", "name": "Czech" }, { "code": "de", "name": "German" }, { "code": "en", "name": "English" }, { "code": "es", "name": "Spanish" }, @@ -13,6 +14,8 @@ { "code": "ru", "name": "Russian" }, { "code": "sl", "name": "Slovenian" }, { "code": "th", "name": "Thai" }, + { "code": "tml", "name": "Tamil" }, + { "code": "tr", "name": "Turkish" }, { "code": "vi", "name": "Vietnamese" }, { "code": "zh_CN", "name": "Mandarin" }, { "code": "zh_TW", "name": "Taiwanese" } diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index d9762a3e9..3a664ec00 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -20,6 +20,9 @@ "addToken": { "message": "トークンを追加" }, + "addTokens": { + "message": "トークンを追加" + }, "amount": { "message": "金額" }, @@ -46,6 +49,9 @@ "balance": { "message": "残高:" }, + "balances": { + "message": "トークン残高" + }, "balanceIsInsufficientGas": { "message": "現在のガス総量に対して残高が不足しています" }, @@ -63,10 +69,10 @@ "message": "購入" }, "buyCoinbase": { - "message": "Coinbaseで購入" + "message": "Coinbaseのサイトで購入" }, "buyCoinbaseExplainer": { - "message": "Coinbaseは、世界的なBitcoin、Ethereum、そしてLitecoinの取引所です。" + "message": "Etherを購入できます。Coinbaseは、世界的なBitcoin、Ethereum、そしてLitecoinの取引所です。" }, "cancel": { "message": "キャンセル" @@ -90,7 +96,7 @@ "message": "トランザクションの確認" }, "continueToCoinbase": { - "message": "Coinbaseで続行" + "message": "Coinbaseを開く" }, "contractDeployment": { "message": "コントラクトのデプロイ" @@ -138,6 +144,9 @@ "customGas": { "message": "ガスのカスタマイズ" }, + "customToken": { + "message": "カスタムトークン" + }, "customize": { "message": "カスタマイズ" }, @@ -154,20 +163,20 @@ "message": "DENとは、あなたのパスワードが暗号化されたMetaMask内のストレージです。" }, "deposit": { - "message": "受取り" + "message": "振込" }, "depositBTC": { - "message": "あなたのBTCを次のアドレスへデポジット:" + "message": "BTCを下記のアドレスへ振込んでください:" }, "depositCoin": { - "message": "あなたの $1を次のアドレスへデポジット", + "message": "$1を下記のアドレスへ振込んでください", "description": "Tells the user what coin they have selected to deposit with shapeshift" }, "depositEth": { - "message": "ETHをデポジット" + "message": "ETHを入金" }, "depositEther": { - "message": "Etherをデポジット" + "message": "Etherを振込" }, "depositFiat": { "message": "法定通貨でデポジット" @@ -176,10 +185,10 @@ "message": "別のアカウントから入金" }, "depositShapeShift": { - "message": "ShapeShiftで入金" + "message": "ShapeShiftで交換" }, "depositShapeShiftExplainer": { - "message": "他の暗号通貨をEtherと交換してMetaMaskのウォレットへ入金できます。アカウント作成は不要です。" + "message": "他の暗号通貨とEtherを交換して、MetaMaskのウォレットへ入金できます。アカウント作成は不要です。" }, "details": { "message": "詳細" @@ -188,10 +197,10 @@ "message": "ダイレクトデポジット" }, "directDepositEther": { - "message": "Etherを直接受け取り" + "message": "Etherを直接入金" }, "directDepositEtherExplainer": { - "message": "Etherをすでにお持ちなら、MetaMaskの新しいウォレットにEtherを送信することができます。" + "message": "既にEtherをお持ちなら、MetaMaskの新しいウォレットにEtherを送信することができます。" }, "done": { "message": "完了" @@ -209,7 +218,7 @@ "message": "パスワードを入力" }, "etherscanView": { - "message": "Etherscanでアカウントを参照" + "message": "Etherscanでアカウントを確認" }, "exchangeRate": { "message": "交換レート" @@ -266,7 +275,7 @@ "message": "必要ガスプライス" }, "getEther": { - "message": "Etherをゲット" + "message": "Etherを取得する" }, "getEtherFromFaucet": { "message": "フォーセットで $1のEtherを得ることができます。", @@ -287,7 +296,7 @@ "message": "トークンを隠す" }, "hideTokenPrompt": { - "message": "トークンを隠しますか??" + "message": "トークンを隠しますか?" }, "howToDeposit": { "message": "どのようにEtherをデポジットしますか?" @@ -300,7 +309,7 @@ "message": "アカウントのインポート" }, "importAccountMsg": { - "message":"追加したアカウントはMetaMaskのアカウントシードフレーズとは関連付けられません。インポートしたアカウントについての詳細は" + "message":"追加したアカウントはMetaMaskのアカウントパスフレーズとは関連付けられません。インポートしたアカウントについての詳細は" }, "importAnAccount": { "message": "アカウントをインポート" @@ -334,13 +343,22 @@ "message": "JSONファイル", "description": "format for importing an account" }, + "keepTrackTokens": { + "message": "MetaMaskアカウントで入手したトークンを検索できます。" + }, "kovan": { "message": "Kovanテストネットワーク" }, + "learnMore": { + "message": "詳細" + }, "lessThanMax": { "message": " $1以下にして下さい。", "description": "helper for inputting hex as decimal input" }, + "likeToAddTokens": { + "message": "トークンを追加しますか?" + }, "limit": { "message": "リミット" }, @@ -371,8 +389,11 @@ "myAccounts": { "message": "マイアカウント" }, + "mustSelectOne": { + "message": "一つ以上のトークンを選択してください。" + }, "needEtherInWallet": { - "message": "MetaMaskを使って分散型アプリケーションを使用するためには、このウォレットにEtherが必要です。" + "message": "MetaMaskで分散型アプリケーションを使用するためには、このウォレットにEtherが必要です。" }, "needImportFile": { "message": "インポートするファイルを選択してください。", @@ -411,7 +432,7 @@ "message": "この名前にはアドレスが設定されていません。" }, "noDeposits": { - "message": "デポジットがありません。" + "message": "振込みがありません。" }, "noTransactionHistory": { "message": "トランザクション履歴がありません。" @@ -445,10 +466,13 @@ "description": "For importing an account from a private key" }, "pasteSeed": { - "message": "シードをここにペーストして下さい!" + "message": "パスフレーズをここにペーストして下さい!" }, "pleaseReviewTransaction": { - "message": "トランザクションをレビューして下さい。" + "message": "トランザクションを確認して下さい。" + }, + "popularTokens": { + "message": "人気のトークン" }, "privateKey": { "message": "秘密鍵", @@ -470,13 +494,13 @@ "message": "もっと読む" }, "receive": { - "message": "受け取る" + "message": "受取" }, "recipientAddress": { "message": "受取人アドレス" }, "refundAddress": { - "message": "あなたの返金先アドレス" + "message": "受取アドレス" }, "rejected": { "message": "拒否されました" @@ -487,12 +511,18 @@ "restoreFromSeed": { "message": "パスフレーズから復元する" }, + "restoreVault": { + "message": "ウォレットを復元する" + }, "required": { "message": "必要です。" }, "retryWithMoreGas": { "message": "より高いガスプライスで再度試して下さい。" }, + "walletSeed": { + "message": "ウォレットのパスフレーズ" + }, "revealSeedWords": { "message": "パスフレーズを表示" }, @@ -519,23 +549,35 @@ "selectService": { "message": "サービスを選択" }, + "selectType": { + "message": "キーの種類" + }, "send": { "message": "送信" }, + "sendETH": { + "message": "ETHの送信" + }, "sendTokens": { "message": "トークンを送る" }, "onlySendToEtherAddress": { "message": "ETHはイーサリウムアカウントのみに送信できます。" }, + "searchTokens": { + "message": "トークンの検索" + }, "sendTokensAnywhere": { "message": "イーサリアムアカウントを持っている人にトークンを送る" }, "settings": { "message": "設定" }, + "info": { + "message": "情報" + }, "shapeshiftBuy": { - "message": "Shapeshiftで買う" + "message": "Shapeshiftで交換" }, "showPrivateKeys": { "message": "秘密鍵を表示" @@ -565,13 +607,13 @@ "message": "送信" }, "takesTooLong": { - "message": "長くかかりすぎていますか?" + "message": "送信に時間がかかりますか?" }, "testFaucet": { "message": "Faucetをテスト" }, "to": { - "message": "宛先" + "message": "送信先" }, "toETHviaShapeShift": { "message": "ShapeShiftで $1をETHにする", @@ -595,6 +637,9 @@ "total": { "message": "合計" }, + "transactions": { + "message": "トランザクション" + }, "transactionMemo": { "message": "トランザクションメモ (オプション)" }, @@ -609,7 +654,7 @@ "description": "Followed by a link (here) to view token balances" }, "typePassword": { - "message": "パスワードタイプ" + "message": "パスワードの入力" }, "uiWelcome": { "message": "新UIへようこそ!(ベータ版)" @@ -646,7 +691,7 @@ "message": "警告" }, "whatsThis": { - "message": "これは何でしょう?" + "message": "この機能について" }, "yourSigRequested": { "message": "あなたの署名がリクエストされています。" diff --git a/app/_locales/tml/messages.json b/app/_locales/tml/messages.json new file mode 100644 index 000000000..fcc418bac --- /dev/null +++ b/app/_locales/tml/messages.json @@ -0,0 +1,912 @@ +{ + "accept": { + "message": "ஏற்கவும்" + }, + "account": { + "message": "கணக்கு" + }, + "accountDetails": { + "message": "கணக்கு விவரங்கள்" + }, + "accountName": { + "message": "கணக்கின் பெயர்" + }, + "address": { + "message": "முகவரி" + }, + "addCustomToken": { + "message": "தனிப்பயன் டோக்கனைச் சேர்க்கவும்" + }, + "addToken": { + "message": "டோக்கனைச் சேர்" + }, + "addTokens": { + "message": "டோக்கன்களைச் சேர்" + }, + "amount": { + "message": "தொகை" + }, + "amountPlusGas": { + "message": "தொகை + எரிவாயு" + }, + "appDescription": { + "message": "எதெரியும் பிரௌசர் நீட்டிப்பு", + "description": "பயன்பாட்டின் விளக்கம்" + }, + "appName": { + "message": "மேடமஸ்க் ", + "description": "பயன்பாட்டின் பெயர்" + }, + "approved": { + "message": "அங்கீகரிக்கப்பட்ட" + }, + "attemptingConnect": { + "message": "இணைக்க முயற்சி செய்க ப்ளாக்சைன்" + }, + "attributions": { + "message": "பண்புகளும்" + }, + "available": { + "message": "கிடைக்கும்" + }, + "back": { + "message": "மீண்டும்" + }, + "balance": { + "message": "இருப்பு:" + }, + "balances": { + "message": "உங்கள் இருப்பு" + }, + "balanceIsInsufficientGas": { + "message": "நடப்பு வாயு மொத்தம் போதுமான சமநிலை" + }, + "beta": { + "message": "பீட்டா" + }, + "betweenMinAndMax": { + "message": "$ 1 க்கும் அதிகமாகவும் அல்லது $ 2 க்கு சமமாகவும் இருக்க வேண்டும்.", + "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி" + }, + "blockiesIdenticon": { + "message": "ப்ளாக்கிஸ் ஐடென்டிகோன் பயன்பாட்டு" + }, + "borrowDharma": { + "message": "தர்மத்துடன் கடன் வாங்குங்கள் (பீட்டா)" + }, + "builtInCalifornia": { + "message": "மேடமஸ்க் வடிவமைக்கப்பட்டு கலிபோர்னியாவில் கட்டப்பட்டுள்ளது." + }, + "buy": { + "message": "வாங்க" + }, + "buyCoinbase": { + "message": "கோஇன்பசே வாங்கவும்" + }, + "buyCoinbaseExplainer": { + "message": "கோஇன்பசே பிறகாய்ன் , எதெரியும் மற்றும் ளிட்டசோன் வாங்க மற்றும் விற்க உலகின் மிகவும் பிரபலமான வழி" + }, + "ok": { + "message": "சரி" + }, + "cancel": { + "message": "ரத்து" + }, + "classicInterface": { + "message": "கிளாசிக் இடைமுகத்தைப் பயன்படுத்தவும்" + }, + "clickCopy": { + "message": "நகலெடுக்க கிளிக் செய்யவும்" + }, + "confirm": { + "message": "உறுதிப்படுத்தவும்" + }, + "confirmed": { + "message": "உறுதி" + }, + "confirmContract": { + "message": "ஒப்பந்தத்தை உறுதிப்படுத்துக" + }, + "confirmPassword": { + "message": "கடவுச்சொல்லை உறுதிப்படுத்துக" + }, + "confirmTransaction": { + "message": "பரிவர்த்தனை உறுதிபடுத்தவும்" + }, + "continue": { + "message": "தொடர்ந்து" + }, + "continueToCoinbase": { + "message": "கோஇன்பசே ஐத் தொடரவும்" + }, + "contractDeployment": { + "message": "ஒப்பந்த வரிசைப்படுத்தல்" + }, + "conversionProgress": { + "message": "மாற்றம் முன்னேற்றம்" + }, + "copiedButton": { + "message": "நகலெடுக்கப்பட்டன" + }, + "copiedClipboard": { + "message": "கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது" + }, + "copiedExclamation": { + "message": "நகலெடுக்கப்பட்டன!" + }, + "copiedSafe": { + "message": "நான் எங்காவது பாதுகாப்பாக நகலெடுத்திருக்கிறேன்" + }, + "copy": { + "message": "நகல்" + }, + "copyToClipboard": { + "message": "கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது" + }, + "copyButton": { + "message": " நகல் " + }, + "copyPrivateKey": { + "message": "இது உங்கள் தனிப்பட்ட விசை (நகலெடுக்க கிளிக் செய்யவும்)" + }, + "create": { + "message": "உருவாக்கவும்" + }, + "createAccount": { + "message": "உங்கள் கணக்கை துவங்குங்கள்" + }, + "createDen": { + "message": "உருவாக்கவும்" + }, + "crypto": { + "message": "கிரிப்டோ", + "description": "பரிமாற்ற வகை (கிரிப்டோசுர்ரென்சிஸ்)" + }, + "currentConversion": { + "message": "தற்போதைய மாற்றம்" + }, + "currentNetwork": { + "message": "தற்போதைய நெட்வொர்க்" + }, + "customGas": { + "message": "எரிவாயுவைத் தனிப்பயனாக்குங்கள்" + }, + "customToken": { + "message": "தனிப்பயன் டோக்கன்" + }, + "customize": { + "message": "தனிப்பயனாக்கலாம்" + }, + "customRPC": { + "message": "விருப்ப RPC ஐ" + }, + "decimalsMustZerotoTen": { + "message": "தசமங்கள் குறைந்தபட்சம் 0, மற்றும் 36 க்கு மேல் இருக்க வேண்டும்." + }, + "decimal": { + "message": "துல்லியத்தின் முடிவு" + }, + "defaultNetwork": { + "message": "எதிர் பரிவர்த்தனைகளுக்கான முன்னிருப்பு வலையமைப்பு முதன்மை நிகரமாகும்." + }, + "denExplainer": { + "message": "உங்கள் DEN என்பது உங்கள் கடவுச்சொல்-மறைகுறியாக்கப்பட்ட சேமிப்பகம் மெட்டாமாஸ்க்கிற்குள்." + }, + "deposit": { + "message": "வைப்புத்தொகை" + }, + "depositBTC": { + "message": "கீழே உங்கள் முகவரிக்கு உங்கள் BTC வைப்போம்:" + }, + "depositCoin": { + "message": "உங்கள் முகவரிக்கு $ 1 ஐ கீழே உள்ளிடவும்", + "description": "சேபஷிபிட் உடன் வைப்புக்குத் தேர்ந்தெடுக்கப்பட்ட நாணயத்தை பயனரிடம் கூறுகிறார்" + }, + "depositEth": { + "message": "வைப்புத்தொகை எது " + }, + "depositEther": { + "message": "வைப்புத்தொகை எதிர் " + }, + "depositFiat": { + "message": "ஃபியட் உடன் வைப்பு" + }, + "depositFromAccount": { + "message": "மற்றொரு கணக்கிலிருந்து வைப்பு" + }, + "depositShapeShift": { + "message": "ShapeShift உடன் வைப்பு" + }, + "depositShapeShiftExplainer": { + "message": "நீங்கள் மற்ற கிரிப்டோகிராரன்கள் சொந்தமாக வைத்திருந்தால், உங்கள் மெட்டாமாஸ்க் பணப்பையில் நேரடியாக ஈதரை வர்த்தகம் செய்யலாம் மற்றும் வைப்பு செய்யலாம். கணக்கு தேவையில்லை." + }, + "details": { + "message": "விவரங்கள்" + }, + "directDeposit": { + "message": "நேரடி வைப்பு" + }, + "directDepositEther": { + "message": "நேரடியாக வைப்புத்தொகை" + }, + "directDepositEtherExplainer": { + "message": "நீங்கள் ஏற்கனவே ஏதெர் இருந்தால், நேரடி வைப்பு மூலம் உங்கள் புதிய பணப்பையில் ஈத்தர் பெற விரைவான வழி." + }, + "done": { + "message": "முடிந்தது" + }, + "downloadStateLogs": { + "message": "மாநில பதிவுகள் பதிவிறக்க" + }, + "dropped": { + "message": "நீக்கப்பட்டார்" + }, + "edit": { + "message": "தொகு" + }, + "editAccountName": { + "message": "கணக்கு பெயரை மாற்றுக" + }, + "emailUs": { + "message": "எங்களுக்கு மின்னஞ்சல்!" + }, + "encryptNewDen": { + "message": "உங்கள் புதிய DEN ஐ குறியாக்குக" + }, + "enterPassword": { + "message": "கடவுச்சொல்லை உள்ளிடவும்" + }, + "enterPasswordConfirm": { + "message": "உறுதிப்படுத்த உங்கள் கடவுச்சொல்லை உள்ளிடவும்" + }, + "passwordNotLongEnough": { + "message": "கடவுச்சொல் போதாது" + }, + "passwordsDontMatch": { + "message": "கடவுச்சொற்கள் பொருந்தாதே" + }, + "etherscanView": { + "message": "Etherscan கணக்கைப் பார்க்கவும்" + }, + "exchangeRate": { + "message": "மாற்று விகிதம்" + }, + "exportPrivateKey": { + "message": "தனியார் விசை ஐ ஏற்றுமதி செய்க" + }, + "exportPrivateKeyWarning": { + "message": "தனிப்பட்ட விசைகளை உங்கள் சொந்த ஆபத்தில் ஏற்றுமதி செய்யுங்கள்." + }, + "failed": { + "message": "தோல்வி" + }, + "fiat": { + "message": "FIAT", + "description": "பரிமாற்ற வகை" + }, + "fileImportFail": { + "message": "கோப்பு இறக்குமதி வேலை செய்யவில்லையா? இங்கே கிளிக் செய்யவும்!", + "description": "JSON கோப்பில் பயனர் கணக்கை தங்கள் கணக்கை இறக்குமதி செய்ய உதவுகிறது" + }, + "followTwitter": { + "message": "Twitter இல் எங்களைப் பின்தொடரவும்" + }, + "from": { + "message": "இருந்து" + }, + "fromToSame": { + "message": "இருந்து மற்றும் முகவரி அதே இருக்க முடியாது" + }, + "fromShapeShift": { + "message": "ShapeShift இலிருந்து" + }, + "gas": { + "message": "எரிவாயு", + "description": "எரிவாயு விலை குறையும்" + }, + "gasFee": { + "message": "எரிவாயு கட்டணம்" + }, + "gasLimit": { + "message": "எரிவாயு வரம்பு" + }, + "gasLimitCalculation": { + "message": "நெட்வொர்க் வெற்றி விகிதங்களின் அடிப்படையில் பரிந்துரைக்கப்பட்ட எரிவாயு வரம்பை நாங்கள் கணக்கிடுகிறோம்." + }, + "gasLimitRequired": { + "message": "எரிவாயு வரம்பு தேவை" + }, + "gasLimitTooLow": { + "message": "எரிவாயு வரம்பு குறைந்தது 21000 ஆக இருக்க வேண்டும்" + }, + "generatingSeed": { + "message": "விதை உருவாக்குகிறது ..." + }, + "gasPrice": { + "message": "எரிவாயு விலை (GWEI)" + }, + "gasPriceCalculation": { + "message": "நெட்வொர்க் வெற்றி விகிதங்களின் அடிப்படையில் பரிந்துரைக்கப்பட்ட எரிவாயு விலைகளை நாங்கள் கணக்கிடுகிறோம்." + }, + "gasPriceRequired": { + "message": "எரிவாயு விலை தேவைப்படுகிறது" + }, + "getEther": { + "message": "ஈத்தர் கிடைக்கும்" + }, + "getEtherFromFaucet": { + "message": "$ 1 க்கு ஒரு குழாய் இருந்து ஈதர் கிடைக்கும்$1", + "description": "ஈத்தர் குழாய் ஐந்து பிணைய பெயர் காட்டுகிறது" + }, + "greaterThanMin": { + "message": "$ 1 க்கும் அதிகமாகவோ அல்லது சமமாகவோ இருக்க வேண்டும்", + "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி" + }, + "here": { + "message": "இங்கே", + "description": "இங்கே-கிளிக் செய்யவும்- மேலும் தகவலுக்கு (troubleTokenBalances செல்கிறது)" + }, + "hereList": { + "message": "இங்கே ஒரு பட்டியல் !!!!" + }, + "hide": { + "message": "மறை" + }, + "hideToken": { + "message": "டோக்கனை மறை" + }, + "hideTokenPrompt": { + "message": "டோக்கனை மறை?" + }, + "howToDeposit": { + "message": "எப்படி ஈத்தர் வைப்பது?" + }, + "holdEther": { + "message": "இது நீங்கள் ஈத்தர் மற்றும் டோக்கன்களை வைத்திருக்க உதவுகிறது, மற்றும் பரவலாக்கப்பட்ட பயன்பாடுகளுக்கு உங்கள் பாலமாக செயல்படுகிறது." + }, + "import": { + "message": "இறக்குமதி", + "description": "தேர்ந்தெடுக்கப்பட்ட கோப்பிலிருந்து ஒரு கணக்கை இறக்குமதி செய்ய பொத்தானை அழுத்தவும்" + }, + "importAccount": { + "message": "கணக்கை இறக்குமதி செய்க" + }, + "importAccountMsg": { + "message":" இறக்குமதி செய்யப்பட்ட கணக்கு உங்கள் முதலில் உருவாக்கப்பட்ட மெட்டாமாஸ்க் கணக்கு விதை மூலம் தொடர்புடையதாக இருக்காது. இறக்குமதி செய்யப்பட்ட கணக்குகள் பற்றி மேலும் அறிக " + }, + "importAnAccount": { + "message": "ஒரு கணக்கை இறக்குமதி செய்க" + }, + "importDen": { + "message": "இறக்குமதி DEN இறக்குமதி" + }, + "imported": { + "message": "இறக்குமதி", + "description": "ஒரு கணக்கு முழுமையாக விசைப்பலகையில் ஏற்றப்பட்டதைக் காட்டுகிறது" + }, + "infoHelp": { + "message": "தகவல் மற்றும் உதவி" + }, + "insufficientFunds": { + "message": "போதுமான பணம் இல்லை." + }, + "insufficientTokens": { + "message": "போதுமான டோக்கன்கள்." + }, + "invalidAddress": { + "message": "தவறான முகவரி" + }, + "invalidAddressRecipient": { + "message": "பெறுநர் முகவரி தவறானது" + }, + "invalidGasParams": { + "message": "தவறான எரிவாயு அளவுருக்கள்" + }, + "invalidInput": { + "message": "தவறான உள்ளீடு.." + }, + "invalidRequest": { + "message": "தவறான கோரிக்கை" + }, + "invalidRPC": { + "message": "தவறான RPC URI" + }, + "jsonFail": { + "message": "ஏதோ தவறு நடந்துவிட்டது. உங்கள் JSON கோப்பு ஒழுங்காக வடிவமைக்கப்பட்டுள்ளது என்பதை உறுதிப்படுத்தவும்" + }, + "jsonFile": { + "message": "JSON கோப்பு", + "description": "ஒரு கணக்கை இறக்குமதி செய்ய வடிவமைக்கப்பட்டுள்ளது" + }, + "keepTrackTokens": { + "message": "உங்கள் மேடமஸ்க் கணக்குடன் நீங்கள் வாங்கிய டோக்கன்களை கண்காணியுங்கள்." + }, + "kovan": { + "message": "கோவன் டெஸ்ட் நெட்வொர்க்" + }, + "knowledgeDataBase": { + "message": "எங்கள் அறிவுத் தளத்தைப் பார்வையிடவும்" + }, + "max": { + "message": "மேக்ஸ்" + }, + "learnMore": { + "message": "மேலும் அறிக" + }, + "lessThanMax": { + "message": "$ 1 க்கும் குறைவாகவோ அல்லது சமமாகவோ இருக்க வேண்டும்.", + "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி" + }, + "likeToAddTokens": { + "message": "இந்த டோக்கன்களைச் சேர்க்க விரும்புகிறீர்களா?" + }, + "links": { + "message": "இணைப்புகள்" + }, + "limit": { + "message": "அளவு" + }, + "loading": { + "message": "ஏற்றுதல் ..." + }, + "loadingTokens": { + "message": "டோக்கன்களை ஏற்றுகிறது ..." + }, + "localhost": { + "message": "லோக்கல் ஹோஸ்ட் 8545" + }, + "login": { + "message": "உள் நுழை" + }, + "logout": { + "message": "வெளியேறு" + }, + "loose": { + "message": "லூஸ்" + }, + "loweCaseWords": { + "message": "விதை வார்த்தைகள் ஸ்மால் எழுத்துகள் மட்டுமே" + }, + "mainnet": { + "message": "முதன்மை எதெரியும் நெட்வொர்க்" + }, + "message": { + "message": "செய்தி" + }, + "metamaskDescription": { + "message": "மேடமஸ்க் என்பது ஒரு பாதுகாப்பான அடையாள வால்ட் எதெரியும்" + }, + "min": { + "message": "குறைந்தபட்ச" + }, + "myAccounts": { + "message": "எனது கணக்குகள்" + }, + "mustSelectOne": { + "message": "குறைந்தது 1 டோக்கனை தேர்ந்தெடுக்க வேண்டும்." + }, + "needEtherInWallet": { + "message": "மேடமஸ்க் ஐ பயன்படுத்தி பரவலாக்கப்பட்ட பயன்பாடுகளுடன் தொடர்பு கொள்ள, உங்கள் பணப்பரிமாற்றத்தில் ஈதர் தேவை." + }, + "needImportFile": { + "message": "இறக்குமதி செய்ய ஒரு கோப்பை நீங்கள் தேர்ந்தெடுக்க வேண்டும்.", + "description": "பயனர் ஒரு கணக்கு முக்கியம் மற்றும் தொடர ஒரு கோப்பு சேர்க்க வேண்டும்" + }, + "needImportPassword": { + "message": "நீங்கள் தேர்ந்தெடுத்த கோப்புக்கு ஒரு கடவுச்சொல்லை உள்ளிட வேண்டும்.", + "description": "ஒரு கணக்கை இறக்குமதி செய்ய கடவுச்சொல் மற்றும் கோப்பு தேவை" + }, + "negativeETH": { + "message": "ETH எதிர்மறை அளவுகளை அனுப்ப முடியாது." + }, + "networks": { + "message": "நெட்வொர்க்ஸ்" + }, + "newAccount": { + "message": "புதிய கணக்கு" + }, + "newAccountNumberName": { + "message": "கணக்கு $ 1", + "description": "கணக்கு கணக்கை உருவாக்குவதற்கு அடுத்த கணக்கின் இயல்புநிலை பெயர் உருவாக்கப்படும்" + }, + "newContract": { + "message": "புதிய ஒப்பந்தம்" + }, + "newPassword": { + "message": "புதிய கடவுச்சொல் (min 8 எழுத்துகள்)" + }, + "newRecipient": { + "message": "புதிய பெறுநர்" + }, + "newRPC": { + "message": "புதிய RPC URL" + }, + "next": { + "message": "அடுத்த" + }, + "noAddressForName": { + "message": "இந்த பெயருக்கான முகவரி அமைக்கப்படவில்லை." + }, + "noDeposits": { + "message": "எந்த வைப்புகளும் கிடைக்கவில்லை" + }, + "noTransactionHistory": { + "message": "பரிவர்த்தனை வரலாறு இல்லை." + }, + "noTransactions": { + "message": "பரிவர்த்தனைகள் இல்லை" + }, + "notStarted": { + "message": "துவங்கவில்லை" + }, + "oldUI": { + "message": "பழைய UI" + }, + "oldUIMessage": { + "message": "நீங்கள் பழைய UI க்கு திரும்பியுள்ளீர்கள். மேல் வலது கீழ்தோன்றும் மெனுவில் உள்ள விருப்பத்தின் மூலம் புதிய UI ஐ மீண்டும் மாறலாம்." + }, + "or": { + "message": "அல்லது", + "description": "ஒரு புதிய கணக்கை உருவாக்க அல்லது இறக்குமதி செய்வதற்கு இடையே தேர்வு" + }, + "passwordCorrect": { + "message": "தயவுசெய்து உங்கள் கடவுச்சொல் சரியானதா என உறுதிப்படுத்தவும்." + }, + "passwordMismatch": { + "message": "கடவுச்சொற்கள் பொருந்தவில்லை", + "description": "கடவுச்சொல் உருவாக்கத்தில், இரண்டு புதிய கடவுச்சொல் புலங்கள் பொருந்தவில்லை" + }, + "passwordShort": { + "message": "கடவுச்சொல் நீண்ட காலமாக இல்லை", + "description": "கடவுச்சொல் உருவாக்கத்தில், பாதுகாப்பானதாக இருக்கும் கடவுச்சொல் போதும்" + }, + "pastePrivateKey": { + "message": "இங்கே உங்கள் தனிப்பட்ட விசை சரத்தை ஒட்டுக:", + "description": "ஒரு தனிப்பட்ட விசை ஒரு கணக்கை இறக்குமதி செய்ய" + }, + "pasteSeed": { + "message": "இங்கே உங்கள் விதை சொற்றொடரை ஒட்டவும்!" + }, + "personalAddressDetected": { + "message": "தனிப்பட்ட முகவரி கண்டறியப்பட்டது. டோக்கன் ஒப்பந்த முகவரியை உள்ளிடவும்." + }, + "pleaseReviewTransaction": { + "message": "உங்கள் பரிவர்த்தனை மதிப்பாய்வு செய்யவும்." + }, + "popularTokens": { + "message": "பிரபலமான டோக்கன்கள்" + }, + "privacyMsg": { + "message": "தனியுரிமை கொள்கை" + }, + "privateKey": { + "message": "தனிப்பட்ட விசை", + "description": "ஒரு கணக்கை இறக்குமதி செய்ய பயன்படுத்த இந்த வகை கோப்பை தேர்ந்தெடுக்கவும்" + }, + "privateKeyWarning": { + "message": "எச்சரிக்கை: இந்த விசையை எப்போதும் வெளியிட வேண்டாம். உங்கள் தனிப்பட்ட விசைகளைக் கொண்ட எவரும் உங்கள் கணக்கில் உள்ள எந்த சொத்துக்களையும் திருடலாம்." + }, + "privateNetwork": { + "message": "தனியார் நெட்வொர்க்" + }, + "qrCode": { + "message": "QR குறியீட்டைக் காட்டு" + }, + "readdToken": { + "message": "உங்கள் கணக்கு விருப்பங்கள் மெனுவில் \"டோக்கனைச் சேர்\" என்பதன் மூலம் நீங்கள் எதிர்காலத்தில் இந்த டோக்கனை மீண்டும் சேர்க்கலாம்." + }, + "readMore": { + "message": "மேலும் வாசிக்க இங்கே." + }, + "readMore2": { + "message": "மேலும் வாசிக்க." + }, + "receive": { + "message": "பெறுக" + }, + "recipientAddress": { + "message": "பெறுநர் முகவரி" + }, + "refundAddress": { + "message": "உங்கள் பணத்தை திருப்பி அனுப்பும் முகவரி" + }, + "rejected": { + "message": "நிராகரிக்கப்பட்டது" + }, + "resetAccount": { + "message": "கணக்கை மீட்டமை" + }, + "restoreFromSeed": { + "message": "விதை வாக்கியத்திலிருந்து மீட்கவும்" + }, + "restoreVault": { + "message": "வால்ட் மீட்கவும்" + }, + "required": { + "message": "தேவையான" + }, + "retryWithMoreGas": { + "message": "இங்கே அதிக எரிவாயு விலை மீண்டும் முயற்சிக்கவும்" + }, + "walletSeed": { + "message": "வால்ட் விதை" + }, + "revealSeedWords": { + "message": "விதை வார்த்தைகள் வெளிப்படுத்த" + }, + "revealSeedWordsWarning": { + "message": "உங்கள் விதை வார்த்தைகள் ஒரு பொது இடத்தில் மீட்க வேண்டாம்! உங்கள் எல்லா கணக்குகளையும் திருட இந்த வார்த்தைகள் பயன்படுத்தப்படலாம்." + }, + "revert": { + "message": "மாற்றியமை" + }, + "rinkeby": { + "message": "ரிங்கெப்ய டெஸ்ட் நெட்வொர்க்" + }, + "ropsten": { + "message": "ரொப்ஸ்டென் டெஸ்ட் நெட்வொர்க்" + }, + "currentRpc": { + "message": "தற்போதைய RPC" + }, + "connectingToMainnet": { + "message": "முக்கிய எதெரியும் நெட்வொர்க் இணைக்கும்" + }, + "connectingToRopsten": { + "message": "ரொப்ஸ்டென் டெஸ்ட் நெட்வொர்க்குடன் இணைக்கிறது" + }, + "connectingToKovan": { + "message": "கோவன் டெஸ்ட் நெட்வொர்க்குடன் இணைத்தல்" + }, + "connectingToRinkeby": { + "message": "ரிங்கெப்ய டெஸ்ட் நெட்வொர்க்குடன் இணைக்கிறது" + }, + "connectingToUnknown": { + "message": "தெரியாத நெட்வொர்க்குடன் இணைக்கிறது" + }, + "sampleAccountName": { + "message": "உதாரணமாக எனது புதிய கணக்கு", + "description": "தங்கள் கணக்கில் மனிதர் படிக்கக்கூடிய பெயரைச் சேர்க்கும் கருத்தை பயனர் புரிந்து கொள்ள உதவுங்கள்" + }, + "save": { + "message": "சேமி" + }, + "reprice_title": { + "message": "ரெப்ரிஸ் பரிவர்த்தனை" + }, + "reprice_subtitle": { + "message": "உங்கள் பரிவர்த்தனைகளை மேலெழுதும் முயற்சியை அதிகரிக்க உங்கள் எரிவாயு விலையை அதிகரிக்கவும்" + }, + "saveAsFile": { + "message": "கோப்பாக சேமிக்கவும்", + "description": "கணக்கு ஏற்றுமதி செயல்முறை" + }, + "saveSeedAsFile": { + "message": "கோப்பு என விதை வார்த்தைகள் சேமிக்கவும்" + }, + "search": { + "message": "தேடல்" + }, + "secretPhrase": { + "message": "உங்கள் பெட்டகத்தை மீட்டெடுப்பதற்காக இங்கே உங்கள் ரகசிய பன்னிரண்டு வார்த்தை சொற்றொடரை உள்ளிடவும்." + }, + "newPassword8Chars": { + "message": "புதிய கடவுச்சொல் (குறைந்தபட்சம் 8 எழுத்துகள்)" + }, + "seedPhraseReq": { + "message": "விதை வாக்கியங்கள் 12 வார்த்தைகள் நீண்டவை" + }, + "select": { + "message": "தேர்வு" + }, + "selectCurrency": { + "message": "நாணயத்தைத் தேர்ந்தெடு" + }, + "selectService": { + "message": "சேவை தேர்ந்தெடுக்கவும்" + }, + "selectType": { + "message": "வகை தேர்ந்தெடு" + }, + "send": { + "message": "அனுப்பு" + }, + "sendETH": { + "message": "ETH ஐ அனுப்பு" + }, + "sendTokens": { + "message": "டோக்கன்களை அனுப்பவும்" + }, + "onlySendToEtherAddress": { + "message": "ETH ஐ ஒரு எதரியும் முகவரிக்கு மட்டும் அனுப்பவும்." + }, + "searchTokens": { + "message": "தேடல் டோக்கன்ஸ்" + }, + "sendTokensAnywhere": { + "message": "யாருடனும் டோக்கன்களை அனுப்பவும் எதெரியும் கணக்கு" + }, + "settings": { + "message": "அமைப்புகள்" + }, + "info": { + "message": "தகவல்" + }, + "shapeshiftBuy": { + "message": "Shapeshift உடன் வாங்கவும்" + }, + "showPrivateKeys": { + "message": "தனிப்பட்ட விசைகளைக் காண்பி" + }, + "showQRCode": { + "message": "QR குறியீட்டைக் காட்டு" + }, + "sign": { + "message": "உள்நுழை" + }, + "signed": { + "message": "கையொப்பமிடப்பட்ட" + }, + "signMessage": { + "message": "செய்தியை பதிவு செய்க" + }, + "signNotice": { + "message": "இந்த செய்தியில் கையொப்பமிடலாம் \nஆபத்தான பக்க விளைவுகள் இருக்கலாம். \n உங்கள் மொத்த கணக்கில் முழுமையாக நம்பக்கூடிய தளங்களில் செய்திகளை மட்டுமே கையொப்பமிடுங்கள். \n இந்த ஆபத்தான முறை எதிர்கால பதிப்பில் அகற்றப்படும்." + }, + "sigRequest": { + "message": "கையொப்பம் கோரிக்கை" + }, + "sigRequested": { + "message": "கையொப்பம் கோரப்பட்டது" + }, + "spaceBetween": { + "message": "வார்த்தைகள் இடையே இடைவெளி மட்டுமே இருக்க முடியும்" + }, + "status": { + "message": "நிலைமை" + }, + "stateLogs": { + "message": "மாநில பதிவுகள்" + }, + "stateLogsDescription": { + "message": "மாநில பதிவுகள் உங்கள் பொது கணக்கு முகவரிகள் மற்றும் பரிமாற்றங்களை அனுப்பியுள்ளன." + }, + "stateLogError": { + "message": "மாநில பதிவுகளை மீட்டெடுப்பதில் பிழை." + }, + "submit": { + "message": "சமர்ப்பி" + }, + "submitted": { + "message": "சமர்ப்பிக்கப்பட்டது" + }, + "supportCenter": { + "message": "எங்கள் ஆதரவு மையத்தைப் பார்வையிடவும்" + }, + "symbolBetweenZeroTen": { + "message": "குறியீடு 0 மற்றும் 10 எழுத்துகளுக்கு இடையில் இருக்க வேண்டும்." + }, + "takesTooLong": { + "message": "நீண்ட நேரம் எடுத்துக்கொள்கிறது?" + }, + "terms": { + "message": "பயன்பாட்டு விதிமுறைகளை" + }, + "testFaucet": { + "message": "சோதனை குழாய்" + }, + "to": { + "message": "பெறுநர்: " + }, + "toETHviaShapeShift": { + "message": "$ 1 முதல் ETH வரை வடிவம்", + "description": "செய்தி தொடக்கத்தில் வைப்பு வகைகளில் நிரப்பப்படும்" + }, + "tokenAddress": { + "message": "டோக்கன் முகவரி" + }, + "tokenAlreadyAdded": { + "message": "டோக்கன் ஏற்கனவே சேர்க்கப்பட்டது." + }, + "tokenBalance": { + "message": "உங்கள் டோக்கன் இருப்பு:" + }, + "tokenSelection": { + "message": "டோக்கன்களைத் தேடு அல்லது பிரபல டோக்கன்களின் பட்டியலிலிருந்து தேர்ந்தெடுக்கவும்." + }, + "tokenSymbol": { + "message": "டோக்கன் சின்னம்" + }, + "tokenWarning1": { + "message": "உங்கள் மேடமஸ்க் கணக்குடன் நீங்கள் வாங்கிய டோக்கன்களை கண்காணியுங்கள். வேறு கணக்கைப் பயன்படுத்தி டோக்கன்களை வாங்கிவிட்டால், அந்த டோக்கன்கள் இங்கே தோன்றாது." + }, + "total": { + "message": "மொத்த" + }, + "transactions": { + "message": "பரிவர்த்தனைகள்" + }, + "transactionError": { + "message": "பரிவர்த்தனை பிழை. விதிமுறை ஒப்பந்தத்தில் விதிவிலக்கு." + }, + "transactionMemo": { + "message": "பரிவர்த்தனை குறிப்பு (விருப்பம்)" + }, + "transactionNumber": { + "message": "பரிவர்த்தனை எண்" + }, + "transfers": { + "message": "இடமாற்றங்கள்" + }, + "troubleTokenBalances": { + "message": "உங்கள் டோக்கன் நிலுவைகளை ஏற்றுவதில் சிக்கல் ஏற்பட்டது. நீங்கள் அவர்களை பார்க்க முடியும்.", + "description": "டோக்கன் நிலுவைகளை காண ஒரு இணைப்பு (இங்கே) தொடர்ந்து" + }, + "twelveWords": { + "message": "இந்த 12 வார்த்தைகள் உங்கள் மெட்டாமாஸ்க் கணக்கை மீட்க ஒரே வழி. \n அவற்றை எங்காவது பாதுகாப்பாகவும் ரகசியமாகவும் சேமிக்கவும்." + }, + "typePassword": { + "message": "உங்கள் கடவுச்சொல்லை தட்டச்சு செய்யவும்" + }, + "uiWelcome": { + "message": "புதிய UI (பீட்டா) க்கு வரவேற்கிறோம்" + }, + "uiWelcomeMessage": { + "message": "இப்போது நீங்கள் புதிய மெட்டாமாஸ்க்கு UI ஐ பயன்படுத்துகிறீர்கள். சுற்றி பாருங்கள், டோக்கன்களை அனுப்பும் புதிய அம்சங்களை முயற்சிக்கவும், உங்களிடம் ஏதேனும் சிக்கல் இருந்தால் எங்களுக்குத் தெரியப்படுத்தவும்." + }, + "unapproved": { + "message": "அங்கீகரிக்கப்படாத" + }, + "unavailable": { + "message": "கிடைக்கவில்லை" + }, + "unknown": { + "message": "தெரியாத" + }, + "unknownNetwork": { + "message": "அறியப்படாத தனியார் நெட்வொர்க்" + }, + "unknownNetworkId": { + "message": "தெரியாத நெட்வொர்க் ஐடி" + }, + "uriErrorMsg": { + "message": "URI கள் சரியான HTTP / HTTPS முன்னொட்டு தேவை." + }, + "usaOnly": { + "message": "அமெரிக்கா மட்டும்", + "description": "இந்த பரிமாற்றத்தைப் பயன்படுத்தி அமெரிக்காவில் உள்ளவர்களுக்கு மட்டுமே இது வரையறுக்கப்படுகிறது" + }, + "usedByClients": { + "message": "பல்வேறு வாடிக்கையாளர்கள் பல்வேறு பயன்படுத்திய" + }, + "useOldUI": { + "message": "உஸ் ஓல்ட் உய் " + }, + "validFileImport": { + "message": "இறக்குமதி செய்ய சரியான கோப்பு தேர்ந்தெடுக்க வேண்டும்." + }, + "vaultCreated": { + "message": "வால்ட் உருவாக்கப்பட்டது" + }, + "viewAccount": { + "message": "கணக்கைக் காட்டு" + }, + "visitWebSite": { + "message": "எங்கள் வலைத்தளத்தைப் பார்வையிடவும்" + }, + "warning": { + "message": "எச்சரிக்கை" + }, + "welcomeBeta": { + "message": "மெட்டாமாஸ்க் பீட்டாவுக்கு வருக" + }, + "whatsThis": { + "message": "இது என்ன?" + }, + "yourSigRequested": { + "message": "உங்கள் கையொப்பம் கோரப்படுகிறது" + }, + "youSign": { + "message": "நீங்கள் கையெழுத்திடுகிறீர்கள்" + } +} diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json new file mode 100644 index 000000000..c2c09bc91 --- /dev/null +++ b/app/_locales/tr/messages.json @@ -0,0 +1,912 @@ +{ + "accept": { + "message": "Kabul et" + }, + "account": { + "message": "Hesap" + }, + "accountDetails": { + "message": "Hesap Detayları" + }, + "accountName": { + "message": "Hesap İsmi" + }, + "address": { + "message": "Adres" + }, + "addCustomToken": { + "message": "Özel jeton ekle" + }, + "addToken": { + "message": "Jeton ekle" + }, + "addTokens": { + "message": "Jetonlar ekle" + }, + "amount": { + "message": "Tutar" + }, + "amountPlusGas": { + "message": "Tutar + Gas" + }, + "appDescription": { + "message": "Ethereum Tarayıcı Uzantısı", + "description": "Uygulama açıklaması" + }, + "appName": { + "message": "MetaMask", + "description": "Uygulama ismi" + }, + "approved": { + "message": "Onaylandı" + }, + "attemptingConnect": { + "message": "Blockchain'e bağlanmayı deniyor" + }, + "attributions": { + "message": "Atıflar" + }, + "available": { + "message": "Müsait" + }, + "back": { + "message": "Geri" + }, + "balance": { + "message": "Bakiye:" + }, + "balances": { + "message": "Jeton bakiyesi" + }, + "balanceIsInsufficientGas": { + "message": "Toplam gas için yetersiz bakiye" + }, + "beta": { + "message": "BETA" + }, + "betweenMinAndMax": { + "message": "$1'e eşit veya daha büyük olmalı ve $2'den küçük veya eşit olmalı", + "description": "Onaltılık sayının ondalık sayı olarak girişi için yardımcı" + }, + "blockiesIdenticon": { + "message": "Blockies Identicon kullan" + }, + "borrowDharma": { + "message": "Dharma (Beta) ile ödünç al" + }, + "builtInCalifornia": { + "message": "MetaMask California'da tasarlandı ve yaratıldı" + }, + "buy": { + "message": "Satın al" + }, + "buyCoinbase": { + "message": "Coinbase'de satın al" + }, + "buyCoinbaseExplainer": { + "message": "Coinbase bitcoin, ethereum, and litecoin alıp satmanın dünyadaki en popüler yolu" + }, + "ok": { + "message": "Tamam" + }, + "cancel": { + "message": "Vazgeç" + }, + "classicInterface": { + "message": "Klasik arayüzü kullan" + }, + "clickCopy": { + "message": "Kopyalamak için tıkla" + }, + "confirm": { + "message": "Onayla" + }, + "confirmed": { + "message": "Onaylandı" + }, + "confirmContract": { + "message": "Sözleşmeyi onayla" + }, + "confirmPassword": { + "message": "Şifreyi onayla" + }, + "confirmTransaction": { + "message": "İşlemi onayla" + }, + "continue": { + "message": "Devam et" + }, + "continueToCoinbase": { + "message": "Coinbase'e devam et" + }, + "contractDeployment": { + "message": "Sözleşme kurulumu" + }, + "conversionProgress": { + "message": "Çevirim devam ediyor" + }, + "copiedButton": { + "message": "Kopyalandı" + }, + "copiedClipboard": { + "message": "Panoya kopyalandı" + }, + "copiedExclamation": { + "message": "Kopyalandı!" + }, + "copiedSafe": { + "message": "Güvenli bir yere kopyaladım" + }, + "copy": { + "message": "Kopyala" + }, + "copyToClipboard": { + "message": "Panoya kopyala" + }, + "copyButton": { + "message": " Kopyala " + }, + "copyPrivateKey": { + "message": "Bu sizin özel anahtarınız (kopyalamak için tıklayın)" + }, + "create": { + "message": "Yarat" + }, + "createAccount": { + "message": "Hesap Oluştur" + }, + "createDen": { + "message": "Yarat" + }, + "crypto": { + "message": "Kripto", + "description": "Kambiyo tipi (kripto para)" + }, + "currentConversion": { + "message": "Geçerli çevirme" + }, + "currentNetwork": { + "message": "Geçerli Ağ" + }, + "customGas": { + "message": "Gas'i özelleştir" + }, + "customToken": { + "message": "Özel Jeton" + }, + "customize": { + "message": "Özelleştir" + }, + "customRPC": { + "message": "Özel RPC" + }, + "decimalsMustZerotoTen": { + "message": "Ondalıklar en azından 0 olmalı ve 36'dan büyük olmamalı." + }, + "decimal": { + "message": "Ondalık hassasiyeti" + }, + "defaultNetwork": { + "message": "Ether işlemleri için varsayılan ağ Main Net." + }, + "denExplainer": { + "message": "DEN'iniz MetaMask içersinde parola-şifrelenmiş deponuzdur." + }, + "deposit": { + "message": "Yatır" + }, + "depositBTC": { + "message": "BTC'inizi aşağıdaki adrese yatırın:" + }, + "depositCoin": { + "message": "$1'nızı aşağıdaki adrese yatırın", + "description": "Kullanıcıya hangi jetonu seçtiyse onu yatırmasını shapeshift ile söyler." + }, + "depositEth": { + "message": "Eth yatır" + }, + "depositEther": { + "message": "Ether yatır" + }, + "depositFiat": { + "message": "Para yatır" + }, + "depositFromAccount": { + "message": "Başka bir hesaptan yatır" + }, + "depositShapeShift": { + "message": "ShapeShift ile yatır" + }, + "depositShapeShiftExplainer": { + "message": "Eğer başka kripto paralara sahipseniz, MetaMask cüzdanınıza direk olarak Ether yatırabilirsiniz. Hesaba gerek yoktur." + }, + "details": { + "message": "Ayrıntılar" + }, + "directDeposit": { + "message": "Direk Yatırma" + }, + "directDepositEther": { + "message": "Direk Ether Yatırma" + }, + "directDepositEtherExplainer": { + "message": "Eğer çoktan Etheriniz varsa, yeni hesabınıza Ether aktarmanın en kolay yolu direk yatırmadır." + }, + "done": { + "message": "Bitti" + }, + "downloadStateLogs": { + "message": "Durum kayıtlarını indir" + }, + "dropped": { + "message": "Bırakıldı" + }, + "edit": { + "message": "Düzenle" + }, + "editAccountName": { + "message": "Hesap ismini düzenle" + }, + "emailUs": { + "message": "Bize e-posta atın!" + }, + "encryptNewDen": { + "message": "Yeni DEN'inizi şifreleyin" + }, + "enterPassword": { + "message": "Parolanızı girin" + }, + "enterPasswordConfirm": { + "message": "Onaylamak için parolanızı girin" + }, + "passwordNotLongEnough": { + "message": "Parola yeterince uzun değil" + }, + "passwordsDontMatch": { + "message": "Parolalar eşleşmiyor" + }, + "etherscanView": { + "message": "Hesabı Etherscan üzerinde izle" + }, + "exchangeRate": { + "message": "Döviz kuru" + }, + "exportPrivateKey": { + "message": "Özel anahtarı ver" + }, + "exportPrivateKeyWarning": { + "message": "Özel anahtarınızı vermek sizin sorumluluğunuzdadır." + }, + "failed": { + "message": "Başarısız oldu" + }, + "fiat": { + "message": "Para", + "description": "Döviz türü" + }, + "fileImportFail": { + "message": "Dosya alma çalışmıyor mu? Buraya tıklayın!", + "description": "Kullanıcıların hesaplarını JSON dosyasından almalarına yardım eder" + }, + "followTwitter": { + "message": "Bizi twitter'da takip edin" + }, + "from": { + "message": "Kimden" + }, + "fromToSame": { + "message": "Kimden ve kime adresi aynı olamaz" + }, + "fromShapeShift": { + "message": "ShapeShift'den" + }, + "gas": { + "message": "Gas", + "description": "Gas maliyetinin kısa indikatörü" + }, + "gasFee": { + "message": "Gas Ücreti" + }, + "gasLimit": { + "message": "Gas Limiti" + }, + "gasLimitCalculation": { + "message": "Önerilen gas limitini ağ başarı oranını baz alarak hesaplıyoruz." + }, + "gasLimitRequired": { + "message": "Gas limiti gereklidir" + }, + "gasLimitTooLow": { + "message": "Gas limiti en az 21000 olmalıdır" + }, + "generatingSeed": { + "message": "Kaynak Oluşturuyor..." + }, + "gasPrice": { + "message": "Gas Fiyatı (GWEI)" + }, + "gasPriceCalculation": { + "message": "Önerilen gas fiyatını ağ başarı oranını baz alarak hesaplıyoruz." + }, + "gasPriceRequired": { + "message": "Gas Fiyatı Gereklidir" + }, + "getEther": { + "message": "Ether Al" + }, + "getEtherFromFaucet": { + "message": "Musluktan $1 karşılığı Ether alın", + "description": "Ether musluğunun ağ ismini gösterir" + }, + "greaterThanMin": { + "message": "must be greater than or equal to $1.", + "description": "helper for inputting hex as decimal input" + }, + "here": { + "message": "burada", + "description": "daha fazla bilgi için -buraya tıklayın- (troubleTokenBalances ile gidiyor)" + }, + "hereList": { + "message": "İşte bir liste!!!!" + }, + "hide": { + "message": "Gizle" + }, + "hideToken": { + "message": "Jetonu gizle" + }, + "hideTokenPrompt": { + "message": "Jetonu gizle?" + }, + "howToDeposit": { + "message": "Ether'i nasıl yatırmak istersiniz?" + }, + "holdEther": { + "message": "Ether ve jeton tutmanızı sağlar ve merkezi olmayan uygulamalar ve sizin aranızda köprü vazifesi görür." + }, + "import": { + "message": "Al", + "description": "Seçilen dosyadan hesap alma düğmesi. " + }, + "importAccount": { + "message": "Hesap Al" + }, + "importAccountMsg": { + "message":" Alınan hesaplar orjinal kaynakifadenizle yarattığınız MetaMask hesabınızla ilişkilendirilmez. Alınan hesaplar ile ilgili daha fazla bilgi edinin " + }, + "importAnAccount": { + "message": "Hesap al" + }, + "importDen": { + "message": "Varolan DEN al" + }, + "imported": { + "message": "Alındı", + "description": "Hesabın keyringe başarı ile alındığını gösteren durum" + }, + "infoHelp": { + "message": "Bilgi ve yardım" + }, + "insufficientFunds": { + "message": "Yetersiz kaynak." + }, + "insufficientTokens": { + "message": "Yetersiz Jeton." + }, + "invalidAddress": { + "message": "Geçersiz adres" + }, + "invalidAddressRecipient": { + "message": "Alıcı adresi geçersiz" + }, + "invalidGasParams": { + "message": "Geçersiz gas parametreleri" + }, + "invalidInput": { + "message": "Geçersiz giriş." + }, + "invalidRequest": { + "message": "Geçersiz istek" + }, + "invalidRPC": { + "message": "Geçersiz RPC URI" + }, + "jsonFail": { + "message": "Birşeyler yanlış gitti. JSON dosyanızın düzgün derlendiğinden emin olun." + }, + "jsonFile": { + "message": "JSON Dosyası", + "description": "Hesap alımı için düzenle" + }, + "keepTrackTokens": { + "message": "MetaMask hesabınızla satın aldığınız jetonların kaydını tutun." + }, + "kovan": { + "message": "Kovan Test Ağı" + }, + "knowledgeDataBase": { + "message": "Bilgi veritabanımızı ziyaret edin" + }, + "max": { + "message": "Maksimum" + }, + "learnMore": { + "message": "Daha fazla bilgi." + }, + "lessThanMax": { + "message": "$1'den az veya eşit olmalıdır.", + "description": "Onaltılık sayıyı ondalık olarak girmek için yardımcı" + }, + "likeToAddTokens": { + "message": "Bu jetonlara adres eklemek ister misiniz?" + }, + "links": { + "message": "Bağlantılar" + }, + "limit": { + "message": "Limit" + }, + "loading": { + "message": "Yükleniyor..." + }, + "loadingTokens": { + "message": "Jetonlar yükleniyor..." + }, + "localhost": { + "message": "Localhost 8545" + }, + "login": { + "message": "Giriş yap" + }, + "logout": { + "message": "Çıkış" + }, + "loose": { + "message": "Gevşek" + }, + "loweCaseWords": { + "message": "kaynak kelimeleri sadece küçük harflerden oluşabilir." + }, + "mainnet": { + "message": "Main Ethereum Ağı" + }, + "message": { + "message": "Mesaj" + }, + "metamaskDescription": { + "message": "MetaMask Ethereum için güvenli bir kimlik kasasıdır." + }, + "min": { + "message": "Minimum" + }, + "myAccounts": { + "message": "Hesaplarım" + }, + "mustSelectOne": { + "message": "En az bir jeton seçilmeli" + }, + "needEtherInWallet": { + "message": "MetaMask kullanarak merkezi olamayan uygulamalarla etkileşmek için cüzdanınızda Ether bulunmalıdır." + }, + "needImportFile": { + "message": "Almak için bir dosya seçmelisiniz.", + "description": "Kullanıcı bir hesap alır ve devam etmek içinbir dosya seçmelidir." + }, + "needImportPassword": { + "message": "Seçilen dosya için bir parola girmelisiniz.", + "description": "Hesap almak için parola ve dosya gerekiyor." + }, + "negativeETH": { + "message": "Negatif ETH miktarları gönderilemez." + }, + "networks": { + "message": "Ağlar" + }, + "newAccount": { + "message": "Yeni Hesap" + }, + "newAccountNumberName": { + "message": "Hesap $1", + "description": "Hesap yaratma ekranındaki bir sonraki hesabın varsayılan ismi" + }, + "newContract": { + "message": "Yeni Sözleşme" + }, + "newPassword": { + "message": "Yeni Parola (min 8 karakter)" + }, + "newRecipient": { + "message": "Yeni alıcı" + }, + "newRPC": { + "message": "Yeni RPC URL" + }, + "next": { + "message": "Sonraki" + }, + "noAddressForName": { + "message": "Bu isim için bir adres tanımlanmamış." + }, + "noDeposits": { + "message": "Yatırma alınmadı" + }, + "noTransactionHistory": { + "message": "İşlem geçmişi yok." + }, + "noTransactions": { + "message": "İşlem yok" + }, + "notStarted": { + "message": "Başlamadı" + }, + "oldUI": { + "message": "Eski UI" + }, + "oldUIMessage": { + "message": "Eski UI'a döndünüz. Yeni UI'a üst sağ sekme menüsündeki seçenek ile dönebilirsiniz." + }, + "or": { + "message": "veya", + "description": "Yeni bir hesap yaratmak veya almak arasındaki seçim" + }, + "passwordCorrect": { + "message": "Lütfen parolanın doğru olduğuna emin olun." + }, + "passwordMismatch": { + "message": "parolalar eşleşmiyor", + "description": "parola yaratma işleminde, iki yeni parola alanı eşleşmiyor." + }, + "passwordShort": { + "message": "parola yeterince uzun değil", + "description": "parola yaratma işleminde, parola güvenli olacak kadar uzun değil." + }, + "pastePrivateKey": { + "message": "Özel anahtar dizinizi buraya yapıştırın:", + "description": "Özel anahtardan hesap almak için" + }, + "pasteSeed": { + "message": "Kaynak ifadenizi buraya yapıştırın!" + }, + "personalAddressDetected": { + "message": "Kişisel adres tespit edilidi. Jeton sözleşme adresini girin." + }, + "pleaseReviewTransaction": { + "message": "Lütfen işleminizi gözden geçirin." + }, + "popularTokens": { + "message": "Popüler Jetonlar" + }, + "privacyMsg": { + "message": "Gizlilik Şartları" + }, + "privateKey": { + "message": "Özel Anahtar", + "description": "Hesap alırken bu tip bir dosya seçin" + }, + "privateKeyWarning": { + "message": "Uyarı: Bu anahtarı kimse ile paylaşmayın. Özel anahtarlarınıza sahip herkes hesaplarınızıdaki tüm varlığınızı çalabilir." + }, + "privateNetwork": { + "message": "Özel Ağ" + }, + "qrCode": { + "message": "QR Kodunu göster" + }, + "readdToken": { + "message": "Gelecekte Bu jetonu hesap seçenekleri menüsünde “Jeton ekle”'ye giderek geri ekleyebilirsiniz." + }, + "readMore": { + "message": "Burada daha fazla okuyun." + }, + "readMore2": { + "message": "Daha fazla okuyun." + }, + "receive": { + "message": "Al" + }, + "recipientAddress": { + "message": "Alıcı adresi" + }, + "refundAddress": { + "message": "İade adresiniz" + }, + "rejected": { + "message": "Rededildi" + }, + "resetAccount": { + "message": "Hesabı sıfıla" + }, + "restoreFromSeed": { + "message": "Kaynak ifadeden geri getir. Restore from seed phrase" + }, + "restoreVault": { + "message": "Kasayı geri getir" + }, + "required": { + "message": "Gerekli" + }, + "retryWithMoreGas": { + "message": "Daha yüksek bir gas fiyatı ile tekrar dene" + }, + "walletSeed": { + "message": "Cüzdan Kaynağı" + }, + "revealSeedWords": { + "message": "Kaynak kelimelerini göster" + }, + "revealSeedWordsWarning": { + "message": "Açık bir yerde kaynak kelimeliriniz geri getirmeyin! Bu kelimeler tüm hesaplarınızı çalmak için kullanılabilir." + }, + "revert": { + "message": "Geri döndür" + }, + "rinkeby": { + "message": "Rinkeby Test Ağı" + }, + "ropsten": { + "message": "Ropsten Test Ağı" + }, + "currentRpc": { + "message": "Geçerli RPC" + }, + "connectingToMainnet": { + "message": "Main Ethereum Ağına bağlanıyor" + }, + "connectingToRopsten": { + "message": "Ropsten Test Ağına bağlanıyor" + }, + "connectingToKovan": { + "message": "Kovan Test Ağına bağlanıyor" + }, + "connectingToRinkeby": { + "message": "Rinkeby Test Ağına bağlanıyor" + }, + "connectingToUnknown": { + "message": "Bilinmeyen Ağa bağlanıyor" + }, + "sampleAccountName": { + "message": "E.g. Yeni hesabım", + "description": "Kullanıcının hesabına okunabilir isim ekleme konseptini anlamasına yardımcı olmak." + }, + "save": { + "message": "Kaydet" + }, + "reprice_title": { + "message": "İşlemi Yeniden Fiyatlandır" + }, + "reprice_subtitle": { + "message": "İşlemizi hızlandırmayı denemek için gas fiyatınızı yükseltin." + }, + "saveAsFile": { + "message": "Dosya olarak kaydet", + "description": "Hesap verme işlemi" + }, + "saveSeedAsFile": { + "message": "Kaynak Kelimelerini Dosya olarak Kaydet" + }, + "search": { + "message": "Ara" + }, + "secretPhrase": { + "message": "Kasanızı geri getirmek için gizli 12 kelimelik ifadenizi giriniz." + }, + "newPassword8Chars": { + "message": "Yeni Parola (minumum 8 karakter)" + }, + "seedPhraseReq": { + "message": "Kaynak ifadeleri 12 kelimedir." + }, + "select": { + "message": "Seç" + }, + "selectCurrency": { + "message": "Döviz Seç" + }, + "selectService": { + "message": "Servis Seç" + }, + "selectType": { + "message": "Tip Seç" + }, + "send": { + "message": "Gönder" + }, + "sendETH": { + "message": "ETH Gönder" + }, + "sendTokens": { + "message": "Jeton Gönder" + }, + "onlySendToEtherAddress": { + "message": "Ethereum adresine sadece ETH gönder." + }, + "searchTokens": { + "message": "Jeton ara" + }, + "sendTokensAnywhere": { + "message": "Ethereum hesabı olan birine Jeton gönder" + }, + "settings": { + "message": "Ayarlar" + }, + "info": { + "message": "Bilgi" + }, + "shapeshiftBuy": { + "message": "Shapeshift ile satın al" + }, + "showPrivateKeys": { + "message": "Özel anahtarları göster" + }, + "showQRCode": { + "message": "QR Kodu göster" + }, + "sign": { + "message": "İmza" + }, + "signed": { + "message": "İmzalandı" + }, + "signMessage": { + "message": "Mesajı İmzala" + }, + "signNotice": { + "message": "Bu mesajı imzalamanın tehlikeli \nyan etkileri olabilir. Tamamen güvendiğiniz sitelerden \ngelen mesajları hesabınızla imzalayınız.\n Bu tehlikeli metod gelecek versiyonlarda çıkarılacaktır. " + }, + "sigRequest": { + "message": "İmza isteği" + }, + "sigRequested": { + "message": "İmza isteniyor" + }, + "spaceBetween": { + "message": "Kelimeler arası sadece bir boşluk olabilir." + }, + "status": { + "message": "Durum" + }, + "stateLogs": { + "message": "Durum Kayıtları" + }, + "stateLogsDescription": { + "message": "Durum kayıtları açık hesap adresinizi ve gönderilen işlemleri içerir." + }, + "stateLogError": { + "message": "Durum kayıtlarını alma hatası" + }, + "submit": { + "message": "Gönder" + }, + "submitted": { + "message": "Gönderildi" + }, + "supportCenter": { + "message": "Destek merkezimizi ziyaret edin" + }, + "symbolBetweenZeroTen": { + "message": "Sembol 0 ve 10 karakter aralığında olmalıdır." + }, + "takesTooLong": { + "message": "Çok mu uzun sürüyor?" + }, + "terms": { + "message": "Kullanım şartları" + }, + "testFaucet": { + "message": "Test Musluğu" + }, + "to": { + "message": "Kime: " + }, + "toETHviaShapeShift": { + "message": "ShapeShift üstünden $1'dan ETH'e", + "description": "system will fill in deposit type in start of message" + }, + "tokenAddress": { + "message": "Jeton Adresi" + }, + "tokenAlreadyAdded": { + "message": "Jeton çoktan eklenmiş." + }, + "tokenBalance": { + "message": "Jeton bakiyeniz:" + }, + "tokenSelection": { + "message": "Jeton arayın veya popüler jeton listemizden seçin." + }, + "tokenSymbol": { + "message": "Jeton Sembolü" + }, + "tokenWarning1": { + "message": "MetaMask hesabınızla aldığınız jetonların kaydını tutun. Başka bir hesapla jetonlar satın aldıysanız, o jetonlar burada gözükmeyecektir." + }, + "total": { + "message": "Toplam" + }, + "transactions": { + "message": "işlemler" + }, + "transactionError": { + "message": "İşlem Hatası. Sözleşme kodundan kural dışı durum fırlatıldı." + }, + "transactionMemo": { + "message": "İşlem notu (opsiyonel)" + }, + "transactionNumber": { + "message": "İşlem numarası" + }, + "transfers": { + "message": "Transferler" + }, + "troubleTokenBalances": { + "message": "Jeton bakiyelerinizi yüklerken sorun yaşadık. Buradan izleyebilirsiniz ", + "description": "Jeton bakiyelerini görmek için bir link (burası) ile takip ediliyor" + }, + "twelveWords": { + "message": "MetaMask hesaplarınızı geri getirmenin tek yolu bu 12 kelimedir.\nBu kelimeleri güvenli ve gizli bir yerde saklayın." + }, + "typePassword": { + "message": "Parolanızı girin" + }, + "uiWelcome": { + "message": "Yeni UI (Beta)'ya hoşgeldiniz" + }, + "uiWelcomeMessage": { + "message": "Şu anda yeni Metamask UI kullanmaktasınız. Gözatın, jeton gönderme gibi yeni özellikleri deneyin ve herhangi bir sorunlar karşılaşırsanız bize haber verin" + }, + "unapproved": { + "message": "Onaylanmadı" + }, + "unavailable": { + "message": "Mevcut değil" + }, + "unknown": { + "message": "Bilinmeyen" + }, + "unknownNetwork": { + "message": "Bilinmeyen özel ağ" + }, + "unknownNetworkId": { + "message": "Bilinmeyen ağ IDsi" + }, + "uriErrorMsg": { + "message": "URIler için HTTP/HTTPS öneki gerekmektedir." + }, + "usaOnly": { + "message": "Sadece ABD", + "description": "Bu dövizi sadece ABD ikamet edenler kullanabilir" + }, + "usedByClients": { + "message": "Farklı istemciler tarafından kullanılmakta" + }, + "useOldUI": { + "message": "Eski UI kullan" + }, + "validFileImport": { + "message": "Almak için geçerli bir dosya seçmelisiniz" + }, + "vaultCreated": { + "message": "Kasa Yaratıldı" + }, + "viewAccount": { + "message": "Hesabı İncele" + }, + "visitWebSite": { + "message": "Web sitemizi ziyaret edin" + }, + "warning": { + "message": "Uyarı" + }, + "welcomeBeta": { + "message": "MetaMask Beta'ya Hoşgeldiniz" + }, + "whatsThis": { + "message": "Bu nedir?" + }, + "yourSigRequested": { + "message": "İmzanız isteniyor" + }, + "youSign": { + "message": "İmzalıyorsunuz" + } +} \ No newline at end of file diff --git a/app/scripts/background.js b/app/scripts/background.js index ec586f642..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 }) @@ -78,6 +82,28 @@ async function loadStateFromPersistence () { diskStore.getState() || migrator.generateInitialState(firstTimeState) + // check if somehow state is empty + // this should never happen but new error reporting suggests that it has + // for a small number of users + // https://github.com/metamask/metamask-extension/issues/3919 + if (versionedData && !versionedData.data) { + // try to recover from diskStore incase only localStore is bad + const diskStoreState = diskStore.getState() + if (diskStoreState && diskStoreState.data) { + // we were able to recover (though it might be old) + versionedData = diskStoreState + const vaultStructure = getObjStructure(versionedData) + raven.captureMessage('MetaMask - Empty vault found - recovered from diskStore', { + // "extra" key is required by Sentry + extra: { vaultStructure }, + }) + } else { + // unable to recover, clear state + versionedData = migrator.generateInitialState(firstTimeState) + raven.captureMessage('MetaMask - Empty vault found - unable to recover') + } + } + // report migration errors to sentry migrator.on('error', (err) => { // get vault structure without secrets @@ -140,9 +166,9 @@ function setupController (initState, initLangCode) { asStream(controller.store), debounce(1000), storeTransform(versionifyData), - storeTransform(syncDataWithExtension), + storeTransform(persistData), (error) => { - log.error('pump hit error', error) + log.error('MetaMask - Persistence pipeline failed', error) } ) @@ -151,7 +177,13 @@ function setupController (initState, initLangCode) { return versionedData } - function syncDataWithExtension(state) { + function persistData (state) { + if (!state) { + throw new Error('MetaMask - updated state is missing', state) + } + if (!state.data) { + throw new Error('MetaMask - updated state does not have data', state) + } if (localStore.isSupported) { localStore.set(state) .catch((err) => { @@ -164,30 +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 { @@ -230,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/address-book.js b/app/scripts/controllers/address-book.js index 6fb4ee114..c91e6b2e4 100644 --- a/app/scripts/controllers/address-book.js +++ b/app/scripts/controllers/address-book.js @@ -4,9 +4,22 @@ const extend = require('xtend') class AddressBookController { - // Controller in charge of managing the address book functionality from the - // recipients field on the send screen. Manages a history of all saved - // addresses and all currently owned addresses. + /** + * Controller in charge of managing the address book functionality from the + * recipients field on the send screen. Manages a history of all saved + * addresses and all currently owned addresses. + * + * @typedef {Object} AddressBookController + * @param {object} opts Overrides the defaults for the initial state of this.store + * @property {array} opts.initState initializes the the state of the AddressBookController. Can contain an + * addressBook property to initialize the addressBook array + * @param {KeyringController} keyringController (Soon to be deprecated) The keyringController used in the current + * MetamaskController. Contains the identities used in this AddressBookController. + * @property {object} store The the store of the current users address book + * @property {array} store.addressBook An array of addresses and nicknames. These are set by the user when sending + * to a new address. + * + */ constructor (opts = {}, keyringController) { const initState = extend({ addressBook: [], @@ -19,7 +32,14 @@ class AddressBookController { // PUBLIC METHODS // - // Sets a new address book in store by accepting a new address and nickname. + /** + * Sets a new address book in store by accepting a new address and nickname. + * + * @param {string} address A hex address of a new account that the user is sending to. + * @param {string} name The name the user wishes to associate with the new account + * @returns {Promise} Promise resolves with undefined + * + */ setAddressBook (address, name) { return this._addToAddressBook(address, name) .then((addressBook) => { @@ -30,14 +50,16 @@ class AddressBookController { }) } - // - // PRIVATE METHODS - // - - - // Performs the logic to add the address and name into the address book. The - // pushed object is an object of two fields. Current behavior does not set an - // upper limit to the number of addresses. + /** + * Performs the logic to add the address and name into the address book. The pushed object is an object of two + * fields. Current behavior does not set an upper limit to the number of addresses. + * + * @private + * @param {string} address A hex address of a new account that the user is sending to. + * @param {string} name The name the user wishes to associate with the new account + * @returns {Promise} Promises the updated addressBook array + * + */ _addToAddressBook (address, name) { const addressBook = this._getAddressBook() const identities = this._getIdentities() @@ -62,14 +84,26 @@ class AddressBookController { return Promise.resolve(addressBook) } - // Internal method to get the address book. Current persistence behavior - // should not require that this method be called from the UI directly. + /** + * Internal method to get the address book. Current persistence behavior should not require that this method be + * called from the UI directly. + * + * @private + * @returns {array} The addressBook array from the store. + * + */ _getAddressBook () { return this.store.getState().addressBook } - // Retrieves identities from the keyring controller in order to avoid - // duplication + /** + * Retrieves identities from the keyring controller in order to avoid + * duplication + * + * @deprecated + * @returns {array} Returns the identies array from the keyringContoller's state + * + */ _getIdentities () { return this.keyringController.memStore.getState().identities } diff --git a/app/scripts/controllers/blacklist.js b/app/scripts/controllers/blacklist.js index df41c90c0..d965f80b8 100644 --- a/app/scripts/controllers/blacklist.js +++ b/app/scripts/controllers/blacklist.js @@ -1,6 +1,7 @@ const ObservableStore = require('obs-store') const extend = require('xtend') const PhishingDetector = require('eth-phishing-detect/src/detector') +const log = require('loglevel') // compute phishing lists const PHISHING_DETECTION_CONFIG = require('eth-phishing-detect/src/config.json') diff --git a/app/scripts/controllers/currency.js b/app/scripts/controllers/currency.js index 36b8808aa..480c08b1c 100644 --- a/app/scripts/controllers/currency.js +++ b/app/scripts/controllers/currency.js @@ -1,11 +1,28 @@ -const ObservableStore = require('obs-store') + const ObservableStore = require('obs-store') const extend = require('xtend') +const log = require('loglevel') // every ten minutes const POLLING_INTERVAL = 600000 class CurrencyController { + /** + * Controller responsible for managing data associated with the currently selected currency. + * + * @typedef {Object} CurrencyController + * @param {object} opts Overrides the defaults for the initial state of this.store + * @property {array} opts.initState initializes the the state of the CurrencyController. Can contain an + * currentCurrency, conversionRate and conversionDate properties + * @property {string} currentCurrency A 2-4 character shorthand that describes a specific currency, currently + * selected by the user + * @property {number} conversionRate The conversion rate from ETH to the selected currency. + * @property {string} conversionDate The date at which the conversion rate was set. Expressed in in milliseconds + * since midnight of January 1, 1970 + * @property {number} conversionInterval The id of the interval created by the scheduleConversionInterval method. + * Used to clear an existing interval on subsequent calls of that method. + * + */ constructor (opts = {}) { const initState = extend({ currentCurrency: 'usd', @@ -19,30 +36,73 @@ class CurrencyController { // PUBLIC METHODS // + /** + * A getter for the currentCurrency property + * + * @returns {string} A 2-4 character shorthand that describes a specific currency, currently selected by the user + * + */ getCurrentCurrency () { return this.store.getState().currentCurrency } + /** + * A setter for the currentCurrency property + * + * @param {string} currentCurrency The new currency to set as the currentCurrency in the store + * + */ setCurrentCurrency (currentCurrency) { this.store.updateState({ currentCurrency }) } + /** + * A getter for the conversionRate property + * + * @returns {string} The conversion rate from ETH to the selected currency. + * + */ getConversionRate () { return this.store.getState().conversionRate } + /** + * A setter for the conversionRate property + * + * @param {number} conversionRate The new rate to set as the conversionRate in the store + * + */ setConversionRate (conversionRate) { this.store.updateState({ conversionRate }) } + /** + * A getter for the conversionDate property + * + * @returns {string} The date at which the conversion rate was set. Expressed in milliseconds since midnight of + * January 1, 1970 + * + */ getConversionDate () { return this.store.getState().conversionDate } + /** + * A setter for the conversionDate property + * + * @param {number} conversionDate The date, expressed in milliseconds since midnight of January 1, 1970, that the + * conversionRate was set + * + */ setConversionDate (conversionDate) { this.store.updateState({ conversionDate }) } + /** + * Updates the conversionRate and conversionDate properties associated with the currentCurrency. Updated info is + * fetched from an external API + * + */ async updateConversionRate () { let currentCurrency try { @@ -58,6 +118,12 @@ class CurrencyController { } } + /** + * Creates a new poll, using setInterval, to periodically call updateConversionRate. The id of the interval is + * stored at the controller's conversionInterval property. If it is called and such an id already exists, the + * previous interval is clear and a new one is created. + * + */ scheduleConversionInterval () { if (this.conversionInterval) { clearInterval(this.conversionInterval) diff --git a/app/scripts/controllers/infura.js b/app/scripts/controllers/infura.js index c6b4c9de2..8f6dd837e 100644 --- a/app/scripts/controllers/infura.js +++ b/app/scripts/controllers/infura.js @@ -1,5 +1,6 @@ const ObservableStore = require('obs-store') const extend = require('xtend') +const log = require('loglevel') // every ten minutes const POLLING_INTERVAL = 10 * 60 * 1000 diff --git a/app/scripts/controllers/network.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/preferences.js b/app/scripts/controllers/preferences.js index b4819d951..d4d508026 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -4,6 +4,21 @@ const extend = require('xtend') class PreferencesController { + /** + * + * @typedef {Object} PreferencesController + * @param {object} opts Overrides the defaults for the initial state of this.store + * @property {object} store The an object containing a users preferences, stored in local storage + * @property {array} store.frequentRpcList A list of custom rpcs to provide the user + * @property {string} store.currentAccountTab Indicates the selected tab in the ui + * @property {array} store.tokens The tokens the user wants display in their token lists + * @property {boolean} store.useBlockie The users preference for blockie identicons within the UI + * @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the + * user wishes to see that feature + * @property {string} store.currentLocale The preferred language locale key + * @property {string} store.selectedAddress A hex string that matches the currently selected address in the app + * + */ constructor (opts = {}) { const initState = extend({ frequentRpcList: [], @@ -17,18 +32,43 @@ class PreferencesController { } // PUBLIC METHODS + /** + * Setter for the `useBlockie` property + * + * @param {boolean} val Whether or not the user prefers blockie indicators + * + */ setUseBlockie (val) { this.store.updateState({ useBlockie: val }) } + /** + * Getter for the `useBlockie` property + * + * @returns {boolean} this.store.useBlockie + * + */ getUseBlockie () { return this.store.getState().useBlockie } + /** + * Setter for the `currentLocale` property + * + * @param {string} key he preferred language locale key + * + */ setCurrentLocale (key) { this.store.updateState({ currentLocale: key }) } + /** + * Setter for the `selectedAddress` property + * + * @param {string} _address A new hex address for an account + * @returns {Promise} Promise resolves with undefined + * + */ setSelectedAddress (_address) { return new Promise((resolve, reject) => { const address = normalizeAddress(_address) @@ -37,10 +77,37 @@ class PreferencesController { }) } + /** + * Getter for the `selectedAddress` property + * + * @returns {string} The hex address for the currently selected account + * + */ getSelectedAddress () { return this.store.getState().selectedAddress } + /** + * Contains data about tokens users add to their account. + * @typedef {Object} AddedToken + * @property {string} address - The hex address for the token contract. Will be all lower cased and hex-prefixed. + * @property {string} symbol - The symbol of the token, usually 3 or 4 capitalized letters + * {@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#symbol} + * @property {boolean} decimals - The number of decimals the token uses. + * {@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#decimals} + */ + + /** + * Adds a new token to the token array, or updates the token if passed an address that already exists. + * Modifies the existing tokens array from the store. All objects in the tokens array array AddedToken objects. + * @see AddedToken {@link AddedToken} + * + * @param {string} rawAddress Hex address of the token contract. May or may not be a checksum address. + * @param {string} symbol The symbol of the token + * @param {number} decimals The number of decimals the token uses. + * @returns {Promise} Promises the new array of AddedToken objects. + * + */ async addToken (rawAddress, symbol, decimals) { const address = normalizeAddress(rawAddress) const newEntry = { address, symbol, decimals } @@ -62,6 +129,13 @@ class PreferencesController { return Promise.resolve(tokens) } + /** + * Removes a specified token from the tokens array. + * + * @param {string} rawAddress Hex address of the token contract to remove. + * @returns {Promise} The new array of AddedToken objects + * + */ removeToken (rawAddress) { const tokens = this.store.getState().tokens @@ -71,10 +145,23 @@ class PreferencesController { return Promise.resolve(updatedTokens) } + /** + * A getter for the `tokens` property + * + * @returns {array} The current array of AddedToken objects + * + */ getTokens () { return this.store.getState().tokens } + /** + * Gets an updated rpc list from this.addToFrequentRpcList() and sets the `frequentRpcList` to this update list. + * + * @param {string} _url The the new rpc url to add to the updated list + * @returns {Promise} Promise resolves with undefined + * + */ updateFrequentRpcList (_url) { return this.addToFrequentRpcList(_url) .then((rpcList) => { @@ -83,6 +170,13 @@ class PreferencesController { }) } + /** + * Setter for the `currentAccountTab` property + * + * @param {string} currentAccountTab Specifies the new tab to be marked as current + * @returns {Promise} Promise resolves with undefined + * + */ setCurrentAccountTab (currentAccountTab) { return new Promise((resolve, reject) => { this.store.updateState({ currentAccountTab }) @@ -90,6 +184,15 @@ class PreferencesController { }) } + /** + * Returns an updated rpcList based on the passed url and the current list. + * The returned list will have a max length of 2. If the _url currently exists it the list, it will be moved to the + * end of the list. The current list is modified and returned as a promise. + * + * @param {string} _url The rpc url to add to the frequentRpcList. + * @returns {Promise} The updated frequentRpcList. + * + */ addToFrequentRpcList (_url) { const rpcList = this.getFrequentRpcList() const index = rpcList.findIndex((element) => { return element === _url }) @@ -105,10 +208,24 @@ class PreferencesController { return Promise.resolve(rpcList) } + /** + * Getter for the `frequentRpcList` property. + * + * @returns {array} An array of one or two rpc urls. + * + */ getFrequentRpcList () { return this.store.getState().frequentRpcList } + /** + * Updates the `featureFlags` property, which is an object. One property within that object will be set to a boolean. + * + * @param {string} feature A key that corresponds to a UI feature. + * @param {boolean} activated Indicates whether or not the UI feature should be displayed + * @returns {Promise} Promises a new object; the updated featureFlags object. + * + */ setFeatureFlag (feature, activated) { const currentFeatureFlags = this.store.getState().featureFlags const updatedFeatureFlags = { @@ -121,6 +238,13 @@ class PreferencesController { return Promise.resolve(updatedFeatureFlags) } + /** + * A getter for the `featureFlags` property + * + * @returns {object} A key-boolean map, where keys refer to features and booleans to whether the + * user wishes to see that feature + * + */ getFeatureFlags () { return this.store.getState().featureFlags } diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js index 4ae3810eb..0c1ee4e38 100644 --- a/app/scripts/controllers/recent-blocks.js +++ b/app/scripts/controllers/recent-blocks.js @@ -2,6 +2,7 @@ const ObservableStore = require('obs-store') const extend = require('xtend') const BN = require('ethereumjs-util').BN const EthQuery = require('eth-query') +const log = require('loglevel') class RecentBlocksController { diff --git a/app/scripts/controllers/shapeshift.js b/app/scripts/controllers/shapeshift.js index 3bbfaa1c5..b2a1462c2 100644 --- a/app/scripts/controllers/shapeshift.js +++ b/app/scripts/controllers/shapeshift.js @@ -1,11 +1,23 @@ const ObservableStore = require('obs-store') const extend = require('xtend') +const log = require('loglevel') // every three seconds when an incomplete tx is waiting const POLLING_INTERVAL = 3000 class ShapeshiftController { + /** + * Controller responsible for managing the list of shapeshift transactions. On construction, it initiates a poll + * that queries a shapeshift.io API for updates to any pending shapeshift transactions + * + * @typedef {Object} ShapeshiftController + * @param {object} opts Overrides the defaults for the initial state of this.store + * @property {array} opts.initState initializes the the state of the ShapeshiftController. Can contain an + * shapeShiftTxList array. + * @property {array} shapeShiftTxList An array of ShapeShiftTx objects + * + */ constructor (opts = {}) { const initState = extend({ shapeShiftTxList: [], @@ -14,21 +26,54 @@ class ShapeshiftController { this.pollForUpdates() } + /** + * Represents, and contains data about, a single shapeshift transaction. + * @typedef {Object} ShapeShiftTx + * @property {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the + * user's Metamask account + * @property {string} depositType - An abbreviation of the type of crypto currency to be deposited. + * @property {string} key - The 'shapeshift' key differentiates this from other types of txs in Metamask + * @property {number} time - The time at which the tx was created + * @property {object} response - Initiated as an empty object, which will be replaced by a Response object. @see {@link + * https://developer.mozilla.org/en-US/docs/Web/API/Response} + */ + // // PUBLIC METHODS // + /** + * A getter for the shapeShiftTxList property + * + * @returns {array} + * + */ getShapeShiftTxList () { const shapeShiftTxList = this.store.getState().shapeShiftTxList return shapeShiftTxList } + /** + * A getter for all ShapeShiftTx in the shapeShiftTxList that have not successfully completed a deposit. + * + * @returns {array} Only includes ShapeShiftTx which has a response property with a status !== complete + * + */ getPendingTxs () { const txs = this.getShapeShiftTxList() const pending = txs.filter(tx => tx.response && tx.response.status !== 'complete') return pending } + /** + * A poll that exists as long as there are pending transactions. Each call attempts to update the data of any + * pendingTxs, and then calls itself again. If there are no pending txs, the recursive call is not made and + * the polling stops. + * + * this.updateTx is used to attempt the update to the pendingTxs in the ShapeShiftTxList, and that updated data + * is saved with saveTx. + * + */ pollForUpdates () { const pendingTxs = this.getPendingTxs() @@ -45,6 +90,15 @@ class ShapeshiftController { }) } + /** + * Attempts to update a ShapeShiftTx with data from a shapeshift.io API. Both the response and time properties + * can be updated. The response property is updated with every call, but the time property is only updated when + * the response status updates to 'complete'. This will occur once the user makes a deposit as the ShapeShiftTx + * depositAddress + * + * @param {ShapeShiftTx} tx The tx to update + * + */ async updateTx (tx) { try { const url = `https://shapeshift.io/txStat/${tx.depositAddress}` @@ -60,6 +114,13 @@ class ShapeshiftController { } } + /** + * Saves an updated to a ShapeShiftTx in the shapeShiftTxList. If the passed ShapeShiftTx is not in the + * shapeShiftTxList, nothing happens. + * + * @param {ShapeShiftTx} tx The updated tx to save, if it exists in the current shapeShiftTxList + * + */ saveTx (tx) { const { shapeShiftTxList } = this.store.getState() const index = shapeShiftTxList.indexOf(tx) @@ -69,6 +130,12 @@ class ShapeshiftController { } } + /** + * Removes a ShapeShiftTx from the shapeShiftTxList + * + * @param {ShapeShiftTx} tx The tx to remove + * + */ removeShapeShiftTx (tx) { const { shapeShiftTxList } = this.store.getState() const index = shapeShiftTxList.indexOf(index) @@ -78,6 +145,14 @@ class ShapeshiftController { this.updateState({ shapeShiftTxList }) } + /** + * Creates a new ShapeShiftTx, adds it to the shapeShiftTxList, and initiates a new poll for updates of pending txs + * + * @param {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the + * user's Metamask account + * @param {string} depositType - An abbreviation of the type of crypto currency to be deposited. + * + */ createShapeShiftTx (depositAddress, depositType) { const state = this.store.getState() let { shapeShiftTxList } = state diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js new file mode 100644 index 000000000..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/index.js b/app/scripts/controllers/transactions/index.js index d7287450b..6e47816f8 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -8,6 +8,7 @@ const TxGasUtil = require('./tx-gas-utils') const PendingTransactionTracker = require('./pending-tx-tracker') const NonceTracker = require('./nonce-tracker') const txUtils = require('./lib/util') +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/buy-eth-url.js b/app/scripts/lib/buy-eth-url.js index b9dde3c28..4e2d0bc79 100644 --- a/app/scripts/lib/buy-eth-url.js +++ b/app/scripts/lib/buy-eth-url.js @@ -1,5 +1,16 @@ module.exports = getBuyEthUrl +/** + * Gives the caller a url at which the user can acquire eth, depending on the network they are in + * + * @param {object} opts Options required to determine the correct url + * @param {string} opts.network The network for which to return a url + * @param {string} opts.amount The amount of ETH to buy on coinbase. Only relevant if network === '1'. + * @param {string} opts.address The address the bought ETH should be sent to. Only relevant if network === '1'. + * @returns {string|undefined} The url at which the user can access ETH, while in the given network. If the passed + * network does not match any of the specified cases, or if no network is given, returns undefined. + * + */ function getBuyEthUrl ({ network, amount, address }) { let url switch (network) { diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 34b603b96..63d27c40e 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -102,7 +102,6 @@ ConfigManager.prototype.setShowSeedWords = function (should) { this.setData(data) } - ConfigManager.prototype.getShouldShowSeedWords = function () { var data = this.getData() return data.showSeedWords @@ -118,6 +117,27 @@ ConfigManager.prototype.getSeedWords = function () { var data = this.getData() return data.seedWords } + +/** + * Called to set the isRevealingSeedWords flag. This happens only when the user chooses to reveal + * the seed words and not during the first time flow. + * @param {boolean} reveal - Value to set the isRevealingSeedWords flag. + */ +ConfigManager.prototype.setIsRevealingSeedWords = function (reveal = false) { + const data = this.getData() + data.isRevealingSeedWords = reveal + this.setData(data) +} + +/** + * Returns the isRevealingSeedWords flag. + * @returns {boolean|undefined} + */ +ConfigManager.prototype.getIsRevealingSeedWords = function () { + const data = this.getData() + return data.isRevealingSeedWords +} + ConfigManager.prototype.setRpcTarget = function (rpcUrl) { var config = this.getConfig() config.provider = { diff --git a/app/scripts/lib/createLoggerMiddleware.js b/app/scripts/lib/createLoggerMiddleware.js index 2707cbd9e..996c3477c 100644 --- a/app/scripts/lib/createLoggerMiddleware.js +++ b/app/scripts/lib/createLoggerMiddleware.js @@ -1,14 +1,20 @@ -// log rpc activity +const log = require('loglevel') + module.exports = createLoggerMiddleware -function createLoggerMiddleware ({ origin }) { - return function loggerMiddleware (req, res, next, end) { - next((cb) => { +/** + * Returns a middleware that logs RPC activity + * @param {{ origin: string }} opts - The middleware options + * @returns {Function} + */ +function createLoggerMiddleware (opts) { + return function loggerMiddleware (/** @type {any} */ req, /** @type {any} */ res, /** @type {Function} */ next) { + next((/** @type {Function} */ cb) => { if (res.error) { log.error('Error in RPC response:\n', res) } if (req.isMetamaskInternal) return - log.info(`RPC (${origin}):`, req, '->', res) + log.info(`RPC (${opts.origin}):`, req, '->', res) cb() }) } diff --git a/app/scripts/lib/createOriginMiddleware.js b/app/scripts/lib/createOriginMiddleware.js index f8bdb2dc2..98bb0e3b3 100644 --- a/app/scripts/lib/createOriginMiddleware.js +++ b/app/scripts/lib/createOriginMiddleware.js @@ -1,9 +1,13 @@ -// append dapp origin domain to request module.exports = createOriginMiddleware -function createOriginMiddleware ({ origin }) { - return function originMiddleware (req, res, next, end) { - req.origin = origin +/** + * Returns a middleware that appends the DApp origin to request + * @param {{ origin: string }} opts - The middleware options + * @returns {Function} + */ +function createOriginMiddleware (opts) { + return function originMiddleware (/** @type {any} */ req, /** @type {any} */ _, /** @type {Function} */ next) { + req.origin = opts.origin next() } } diff --git a/app/scripts/lib/enums.js b/app/scripts/lib/enums.js new file mode 100644 index 000000000..0a3afca47 --- /dev/null +++ b/app/scripts/lib/enums.js @@ -0,0 +1,9 @@ +const ENVIRONMENT_TYPE_POPUP = 'popup' +const ENVIRONMENT_TYPE_NOTIFICATION = 'notification' +const ENVIRONMENT_TYPE_FULLSCREEN = 'fullscreen' + +module.exports = { + ENVIRONMENT_TYPE_POPUP, + ENVIRONMENT_TYPE_NOTIFICATION, + ENVIRONMENT_TYPE_FULLSCREEN, +} diff --git a/app/scripts/lib/environment-type.js b/app/scripts/lib/environment-type.js deleted file mode 100644 index 7966926eb..000000000 --- a/app/scripts/lib/environment-type.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = function environmentType () { - const url = window.location.href - if (url.match(/popup.html$/)) { - return 'popup' - } else if (url.match(/home.html$/)) { - return 'responsive' - } else { - return 'notification' - } -} diff --git a/app/scripts/lib/events-proxy.js b/app/scripts/lib/events-proxy.js index c0a490b05..f83773ccc 100644 --- a/app/scripts/lib/events-proxy.js +++ b/app/scripts/lib/events-proxy.js @@ -1,26 +1,37 @@ +/** + * Returns an EventEmitter that proxies events from the given event emitter + * @param {any} eventEmitter + * @param {object} listeners - The listeners to proxy to + * @returns {any} + */ module.exports = function createEventEmitterProxy (eventEmitter, listeners) { let target = eventEmitter const eventHandlers = listeners || {} - const proxy = new Proxy({}, { - get: (obj, name) => { + const proxy = /** @type {any} */ (new Proxy({}, { + get: (_, name) => { // intercept listeners if (name === 'on') return addListener if (name === 'setTarget') return setTarget if (name === 'proxyEventHandlers') return eventHandlers - return target[name] + return (/** @type {any} */ (target))[name] }, - set: (obj, name, value) => { + set: (_, name, value) => { target[name] = value return true }, - }) - function setTarget (eventEmitter) { + })) + function setTarget (/** @type {EventEmitter} */ eventEmitter) { target = eventEmitter // migrate listeners Object.keys(eventHandlers).forEach((name) => { - eventHandlers[name].forEach((handler) => target.on(name, handler)) + /** @type {Array} */ (eventHandlers[name]).forEach((handler) => target.on(name, handler)) }) } + /** + * Attaches a function to be called whenever the specified event is emitted + * @param {string} name + * @param {Function} handler + */ function addListener (name, handler) { if (!eventHandlers[name]) eventHandlers[name] = [] eventHandlers[name].push(handler) diff --git a/app/scripts/lib/get-first-preferred-lang-code.js b/app/scripts/lib/get-first-preferred-lang-code.js index e3635434e..5473fccf0 100644 --- a/app/scripts/lib/get-first-preferred-lang-code.js +++ b/app/scripts/lib/get-first-preferred-lang-code.js @@ -4,6 +4,13 @@ const allLocales = require('../../_locales/index.json') const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().replace('_', '-')) +/** + * Returns a preferred language code, based on settings within the user's browser. If we have no translations for the + * users preferred locales, 'en' is returned. + * + * @returns {Promise} Promises a locale code, either one from the user's preferred list that we have a translation for, or 'en' + * + */ async function getFirstPreferredLangCode () { const userPreferredLocaleCodes = await promisify( extension.i18n.getAcceptLanguages, diff --git a/app/scripts/lib/hex-to-bn.js b/app/scripts/lib/hex-to-bn.js index 184217279..b28746920 100644 --- a/app/scripts/lib/hex-to-bn.js +++ b/app/scripts/lib/hex-to-bn.js @@ -1,6 +1,11 @@ -const ethUtil = require('ethereumjs-util') +const ethUtil = (/** @type {object} */ (require('ethereumjs-util'))) const BN = ethUtil.BN +/** + * Returns a [BinaryNumber]{@link BN} representation of the given hex value + * @param {string} hex + * @return {any} + */ module.exports = function hexToBn (hex) { return new BN(ethUtil.stripHexPrefix(hex), 16) } diff --git a/app/scripts/lib/is-popup-or-notification.js b/app/scripts/lib/is-popup-or-notification.js deleted file mode 100644 index e2999411f..000000000 --- a/app/scripts/lib/is-popup-or-notification.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = function isPopupOrNotification () { - const url = window.location.href - // if (url.match(/popup.html$/) || url.match(/home.html$/)) { - // Below regexes needed for feature toggles (e.g. see line ~340 in ui/app/app.js) - // Revert below regexes to above commented out regexes before merge to master - if (url.match(/popup.html(?:\?.+)*$/) || url.match(/home.html(?:\?.+)*$/)) { - return 'popup' - } else { - return 'notification' - } -} diff --git a/app/scripts/lib/local-store.js b/app/scripts/lib/local-store.js index 5b47985f6..139ff86bd 100644 --- a/app/scripts/lib/local-store.js +++ b/app/scripts/lib/local-store.js @@ -1,10 +1,13 @@ -// We should not rely on local storage in an extension! -// We should use this instead! -// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/storage/local - const extension = require('extensionizer') +const log = require('loglevel') +/** + * A wrapper around the extension's storage local API + */ module.exports = class ExtensionStore { + /** + * @constructor + */ constructor() { this.isSupported = !!(extension.storage.local) if (!this.isSupported) { @@ -12,6 +15,10 @@ module.exports = class ExtensionStore { } } + /** + * Returns all of the keys currently saved + * @return {Promise<*>} + */ async get() { if (!this.isSupported) return undefined const result = await this._get() @@ -24,14 +31,24 @@ module.exports = class ExtensionStore { } } + /** + * Sets the key in local state + * @param {object} state - The state to set + * @return {Promise} + */ async set(state) { return this._set(state) } + /** + * Returns all of the keys currently saved + * @private + * @return {object} the key-value map from local storage + */ _get() { const local = extension.storage.local return new Promise((resolve, reject) => { - local.get(null, (result) => { + local.get(null, (/** @type {any} */ result) => { const err = extension.runtime.lastError if (err) { reject(err) @@ -42,6 +59,12 @@ module.exports = class ExtensionStore { }) } + /** + * Sets the key in local state + * @param {object} obj - The key to set + * @return {Promise} + * @private + */ _set(obj) { const local = extension.storage.local return new Promise((resolve, reject) => { @@ -57,6 +80,11 @@ module.exports = class ExtensionStore { } } +/** + * Returns whether or not the given object contains no keys + * @param {object} obj - The object to check + * @returns {boolean} + */ function isEmpty(obj) { return Object.keys(obj).length === 0 } diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index 85c2717ea..345ca8001 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -1,7 +1,23 @@ const EventEmitter = require('events') +/** + * @typedef {object} Migration + * @property {number} version - The migration version + * @property {Function} migrate - Returns a promise of the migrated data + */ + +/** + * @typedef {object} MigratorOptions + * @property {Array} [migrations] - The list of migrations to apply + * @property {number} [defaultVersion] - The version to use in the initial state + */ + class Migrator extends EventEmitter { + /** + * @constructor + * @param {MigratorOptions} opts + */ constructor (opts = {}) { super() const migrations = opts.migrations || [] @@ -42,19 +58,30 @@ class Migrator extends EventEmitter { return versionedData - // migration is "pending" if it has a higher - // version number than currentVersion + /** + * Returns whether or not the migration is pending + * + * A migration is considered "pending" if it has a higher + * version number than the current version. + * @param {Migration} migration + * @returns {boolean} + */ function migrationIsPending (migration) { return migration.version > versionedData.meta.version } } - generateInitialState (initState) { + /** + * Returns the initial state for the migrator + * @param {object} [data] - The data for the initial state + * @returns {{meta: {version: number}, data: any}} + */ + generateInitialState (data) { return { meta: { version: this.defaultVersion, }, - data: initState, + data, } } diff --git a/app/scripts/lib/nodeify.js b/app/scripts/lib/nodeify.js index 9b595d93c..25be6537b 100644 --- a/app/scripts/lib/nodeify.js +++ b/app/scripts/lib/nodeify.js @@ -1,6 +1,14 @@ const promiseToCallback = require('promise-to-callback') const noop = function () {} +/** + * A generator that returns a function which, when passed a promise, can treat that promise as a node style callback. + * The prime advantage being that callbacks are better for error handling. + * + * @param {Function} fn The function to handle as a callback + * @param {Object} context The context in which the fn is to be called, most often a this reference + * + */ module.exports = function nodeify (fn, context) { return function () { const args = [].slice.call(arguments) diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index 6602f5aa8..43a7d0b42 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -3,6 +3,7 @@ const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') const createId = require('./random-id') const hexRe = /^[0-9A-Fa-f]+$/g +const log = require('loglevel') module.exports = class PersonalMessageManager extends EventEmitter { diff --git a/app/scripts/lib/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/stream-utils.js b/app/scripts/lib/stream-utils.js index 8bb0b4f3c..3dbc064b5 100644 --- a/app/scripts/lib/stream-utils.js +++ b/app/scripts/lib/stream-utils.js @@ -8,20 +8,34 @@ module.exports = { setupMultiplex: setupMultiplex, } +/** + * Returns a stream transform that parses JSON strings passing through + * @return {stream.Transform} + */ function jsonParseStream () { - return Through.obj(function (serialized, encoding, cb) { + return Through.obj(function (serialized, _, cb) { this.push(JSON.parse(serialized)) cb() }) } +/** + * Returns a stream transform that calls {@code JSON.stringify} + * on objects passing through + * @return {stream.Transform} the stream transform + */ function jsonStringifyStream () { - return Through.obj(function (obj, encoding, cb) { + return Through.obj(function (obj, _, cb) { this.push(JSON.stringify(obj)) cb() }) } +/** + * Sets up stream multiplexing for the given stream + * @param {any} connectionStream - the stream to mux + * @return {stream.Stream} the multiplexed stream + */ function setupMultiplex (connectionStream) { const mux = new ObjectMultiplex() pump( diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index 8b760790e..60042155e 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -3,7 +3,7 @@ const ObservableStore = require('obs-store') const createId = require('./random-id') const assert = require('assert') const sigUtil = require('eth-sig-util') - +const log = require('loglevel') module.exports = class TypedMessageManager extends EventEmitter { constructor (opts) { diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js index 6dee9edf0..431d1e59c 100644 --- a/app/scripts/lib/util.js +++ b/app/scripts/lib/util.js @@ -1,20 +1,53 @@ const ethUtil = require('ethereumjs-util') const assert = require('assert') const BN = require('bn.js') +const { + ENVIRONMENT_TYPE_POPUP, + ENVIRONMENT_TYPE_NOTIFICATION, + ENVIRONMENT_TYPE_FULLSCREEN, +} = require('./enums') -module.exports = { - getStack, - sufficientBalance, - hexToBn, - bnToHex, - BnMultiplyByFraction, -} - +/** + * Generates an example stack trace + * + * @returns {string} A stack trace + * + */ function getStack () { const stack = new Error('Stack trace generator - not an error').stack return stack } +/** + * Used to determine the window type through which the app is being viewed. + * - 'popup' refers to the extension opened through the browser app icon (in top right corner in chrome and firefox) + * - 'responsive' refers to the main browser window + * - 'notification' refers to the popup that appears in its own window when taking action outside of metamask + * + * @returns {string} A single word label that represents the type of window through which the app is being viewed + * + */ +const getEnvironmentType = (url = window.location.href) => { + if (url.match(/popup.html(?:\?.+)*$/)) { + return ENVIRONMENT_TYPE_POPUP + } else if (url.match(/home.html(?:\?.+)*$/) || url.match(/home.html(?:#.*)*$/)) { + return ENVIRONMENT_TYPE_FULLSCREEN + } else { + return ENVIRONMENT_TYPE_NOTIFICATION + } +} + +/** + * Checks whether a given balance of ETH, represented as a hex string, is sufficient to pay a value plus a gas fee + * + * @param {object} txParams Contains data about a transaction + * @param {string} txParams.gas The gas for a transaction + * @param {string} txParams.gasPrice The price per gas for the transaction + * @param {string} txParams.value The value of ETH to send + * @param {string} hexBalance A balance of ETH represented as a hex string + * @returns {boolean} Whether the balance is greater than or equal to the value plus the value of gas times gasPrice + * + */ function sufficientBalance (txParams, hexBalance) { // validate hexBalance is a hex string assert.equal(typeof hexBalance, 'string', 'sufficientBalance - hexBalance is not a hex string') @@ -29,16 +62,48 @@ function sufficientBalance (txParams, hexBalance) { return balance.gte(maxCost) } +/** + * Converts a BN object to a hex string with a '0x' prefix + * + * @param {BN} inputBn The BN to convert to a hex string + * @returns {string} A '0x' prefixed hex string + * + */ function bnToHex (inputBn) { return ethUtil.addHexPrefix(inputBn.toString(16)) } +/** + * Converts a hex string to a BN object + * + * @param {string} inputHex A number represented as a hex string + * @returns {Object} A BN object + * + */ function hexToBn (inputHex) { return new BN(ethUtil.stripHexPrefix(inputHex), 16) } +/** + * Used to multiply a BN by a fraction + * + * @param {BN} targetBN The number to multiply by a fraction + * @param {number|string} numerator The numerator of the fraction multiplier + * @param {number|string} denominator The denominator of the fraction multiplier + * @returns {BN} The product of the multiplication + * + */ function BnMultiplyByFraction (targetBN, numerator, denominator) { const numBN = new BN(numerator) const denomBN = new BN(denominator) return targetBN.mul(numBN).div(denomBN) } + +module.exports = { + getStack, + getEnvironmentType, + sufficientBalance, + hexToBn, + bnToHex, + BnMultiplyByFraction, +} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b96acc9da..782bc50ac 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,17 @@ module.exports = class MetamaskController extends EventEmitter { const vault = this.keyringController.store.getState().vault const isInitialized = (!!wallet || !!vault) - return extend( - { - isInitialized, - }, - this.networkController.store.getState(), - this.accountTracker.store.getState(), - this.txController.memStore.getState(), - this.messageManager.memStore.getState(), - this.personalMessageManager.memStore.getState(), - this.typedMessageManager.memStore.getState(), - this.keyringController.memStore.getState(), - this.balancesController.store.getState(), - this.preferencesController.store.getState(), - this.addressBookController.store.getState(), - this.currencyController.store.getState(), - this.noticeController.memStore.getState(), - this.infuraController.store.getState(), - this.recentBlocksController.store.getState(), - // config manager - this.configManager.getConfig(), - this.shapeshiftController.store.getState(), - { + return { + ...{ isInitialized }, + ...this.memStore.getFlatState(), + ...this.configManager.getConfig(), + ...{ lostAccounts: this.configManager.getLostAccounts(), seedWords: this.configManager.getSeedWords(), forgottenPassword: this.configManager.getPasswordForgotten(), - } - ) + isRevealingSeedWords: Boolean(this.configManager.getIsRevealingSeedWords()), + }, + } } /** @@ -372,6 +348,7 @@ module.exports = class MetamaskController extends EventEmitter { clearSeedWordCache: this.clearSeedWordCache.bind(this), resetAccount: nodeify(this.resetAccount, this), importAccountWithStrategy: this.importAccountWithStrategy.bind(this), + setIsRevealingSeedWords: this.configManager.setIsRevealingSeedWords.bind(this.configManager), // vault management submitPassword: nodeify(keyringController.submitPassword, keyringController), @@ -1057,4 +1034,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/platforms/sw.js b/app/scripts/platforms/sw.js index 007d8dc5b..56c5f2774 100644 --- a/app/scripts/platforms/sw.js +++ b/app/scripts/platforms/sw.js @@ -1,20 +1,25 @@ - class SwPlatform { - - // - // Public - // - + /** + * Reloads the platform + */ reload () { - // you cant actually do this - global.location.reload() + // TODO: you can't actually do this + /** @type {any} */ (global).location.reload() } - openWindow ({ url }) { - // this doesnt actually work - global.open(url, '_blank') + /** + * Opens a window + * @param {{url: string}} opts - The window options + */ + openWindow (opts) { + // TODO: this doesn't actually work + /** @type {any} */ (global).open(opts.url, '_blank') } + /** + * Returns the platform version + * @returns {string} + */ getVersion () { return '' } diff --git a/app/scripts/platforms/window.js b/app/scripts/platforms/window.js index 1527c008b..943b2a703 100644 --- a/app/scripts/platforms/window.js +++ b/app/scripts/platforms/window.js @@ -1,18 +1,23 @@ - class WindowPlatform { - - // - // Public - // - + /** + * Reload the platform + */ reload () { - global.location.reload() + /** @type {any} */ (global).location.reload() } - openWindow ({ url }) { - global.open(url, '_blank') + /** + * Opens a window + * @param {{url: string}} opts - The window options + */ + openWindow (opts) { + /** @type {any} */ (global).open(opts.url, '_blank') } + /** + * Returns the platform version + * @returns {string} + */ getVersion () { return '' } 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/mock-dev.js b/development/mock-dev.js index a1fb3a86d..f332633d5 100644 --- a/development/mock-dev.js +++ b/development/mock-dev.js @@ -36,15 +36,28 @@ log.setLevel('debug') // const qs = require('qs') -let queryString = qs.parse(window.location.href.split('#')[1]) -let selectedView = queryString.view || 'first time' +const routerPath = window.location.href.split('#')[1] +let queryString = {} +let selectedView + +if (routerPath) { + queryString = qs.parse(routerPath.split('?')[1]) +} + +selectedView = queryString.view || 'first time' const firstState = states[selectedView] updateQueryParams(selectedView) -function updateQueryParams(newView) { +function updateQueryParams (newView) { queryString.view = newView const params = qs.stringify(queryString) - window.location.href = window.location.href.split('#')[0] + `#${params}` + const locationPaths = window.location.href.split('#') + const routerPath = locationPaths[1] || '' + const newPath = locationPaths[0] + '#' + routerPath.split('?')[0] + `?${params}` + + if (window.location.href !== newPath) { + window.location.href = newPath + } } // diff --git a/development/states/currency-localization.json b/development/states/currency-localization.json new file mode 100644 index 000000000..302e24c11 --- /dev/null +++ b/development/states/currency-localization.json @@ -0,0 +1,134 @@ +{ + "metamask": { + "isInitialized": true, + "isUnlocked": true, + "featureFlags": {"betaUI": true}, + "rpcTarget": "https://rawtestrpc.metamask.io/", + "identities": { + "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": { + "address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "name": "Send Account 1" + }, + "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": { + "address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "name": "Send Account 2" + }, + "0x2f8d4a878cfa04a6e60d46362f5644deab66572d": { + "address": "0x2f8d4a878cfa04a6e60d46362f5644deab66572d", + "name": "Send Account 3" + }, + "0xd85a4b6a394794842887b8284293d69163007bbb": { + "address": "0xd85a4b6a394794842887b8284293d69163007bbb", + "name": "Send Account 4" + } + }, + "unapprovedTxs": {}, + "currentCurrency": "USD", + "conversionRate": 19855, + "conversionDate": 1489013762, + "noActiveNotices": true, + "frequentRpcList": [], + "network": "3", + "accounts": { + "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": { + "code": "0x", + "balance": "0x47c9d71831c76efe", + "nonce": "0x1b", + "address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825" + }, + "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": { + "code": "0x", + "balance": "0x37452b1315889f80", + "nonce": "0xa", + "address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb" + }, + "0x2f8d4a878cfa04a6e60d46362f5644deab66572d": { + "code": "0x", + "balance": "0x30c9d71831c76efe", + "nonce": "0x1c", + "address": "0x2f8d4a878cfa04a6e60d46362f5644deab66572d" + }, + "0xd85a4b6a394794842887b8284293d69163007bbb": { + "code": "0x", + "balance": "0x0", + "nonce": "0x0", + "address": "0xd85a4b6a394794842887b8284293d69163007bbb" + } + }, + "addressBook": [ + { + "address": "0x06195827297c7a80a443b6894d3bdb8824b43896", + "name": "Address Book Account 1" + } + ], + "tokens": [], + "transactions": {}, + "selectedAddressTxList": [], + "unapprovedMsgs": {}, + "unapprovedMsgCount": 0, + "unapprovedPersonalMsgs": {}, + "unapprovedPersonalMsgCount": 0, + "keyringTypes": [ + "Simple Key Pair", + "HD Key Tree" + ], + "keyrings": [ + { + "type": "HD Key Tree", + "accounts": [ + "fdea65c8e26263f6d9a1b5de9555d2931a33b825", + "c5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "2f8d4a878cfa04a6e60d46362f5644deab66572d" + ] + }, + { + "type": "Simple Key Pair", + "accounts": [ + "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825" + ] + } + ], + "selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "currentCurrency": "PHP", + "provider": { + "type": "testnet" + }, + "shapeShiftTxList": [], + "lostAccounts": [], + "send": { + "gasLimit": null, + "gasPrice": null, + "gasTotal": "0xb451dc41b578", + "tokenBalance": null, + "from": "", + "to": "", + "amount": "0x0", + "memo": "", + "errors": {}, + "maxModeOn": false, + "editingTransactionId": null + }, + "currentLocale": "en" + }, + "appState": { + "menuOpen": false, + "currentView": { + "name": "accountDetail", + "detailView": null, + "context": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc" + }, + "accountDetail": { + "subview": "transactions" + }, + "modal": { + "modalState": {}, + "previousModalState": {} + }, + "transForward": true, + "isLoading": false, + "warning": null, + "scrollToBottom": false, + "forgottenPassword": null + }, + "identities": {} +} diff --git a/development/states/tx-list-items.js b/development/states/tx-list-items.json similarity index 100% rename from development/states/tx-list-items.js rename to development/states/tx-list-items.json 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 + + + + + + + + + + + + + + + + + + + + + +
+ +

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 + + + + + + + + + + + + + + + + + + + + + +
+ +

Global

+ + + + + + + +
+ +
+ +

+ +

+ + +
+ +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + +

Type Definitions

+ + + +

MetaMaskOptions

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
platform + + +Platform + + + +

An object including platform-specific functions.

+ + + + + + + + +
Type:
+
    +
  • + +object + + +
  • +
+ + + + + + + + +

Platform

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
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:
+
    +
  • + +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/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 + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +

+ controllers/transactions.js +

+ + +
+ +
+
+ + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
License:
+
  • MIT
+ + + + + + + + + +
+ + + + + +

The transaction controller. Receives incoming transactions, and emits events for various states of their processing.

+ + + + +
+ + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + +
+ +
+ +

+ metamask-controller.js +

+ + +
+ +
+
+ + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
License:
+
  • MIT
+ + + + + + + + + +
+ + + + + +

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 + + + + + + + + + + + + + + + + + + + + + +
+ +

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 + + + + + + + + + + + + + + + + + + + + + +
+ +

exports

+ + + + + + + +
+ +
+ +

+ exports +

+ + +
+ +
+
+ + + + + +

new exports(opts)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
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 + + + + + + + + + + + + + + + + + + + + + +
+ +

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",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), +["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", +/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), +["cv","py"]);k(u({keywords:"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",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", +hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= +!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p0&&(o.verbose("Modifying existing settings",s),s[r]("setting",t,n))))})},settings:function(t,n,i){n="string"==typeof n?[n]:n||d.modules,i=void 0===i||i,e.each(n,function(n,a){var r;o.moduleExists(a)&&(o.verbose("Changing default setting",t,a),e.extend(!0,e.fn[a].settings,t),i&&f&&(r=e(":data(module-"+f+")"),r.length>0&&(o.verbose("Modifying existing settings",r),r[a]("setting",t))))})}},enable:{console:function(){o.console(!0)},debug:function(e,t){e=e||d.modules,o.debug("Enabling debug for modules",e),o.change.setting("debug",!0,e,t)},verbose:function(e,t){e=e||d.modules,o.debug("Enabling verbose debug for modules",e),o.change.setting("verbose",!0,e,t)}},disable:{console:function(){o.console(!1)},debug:function(e,t){e=e||d.modules,o.debug("Disabling debug for modules",e),o.change.setting("debug",!1,e,t)},verbose:function(e,t){e=e||d.modules,o.debug("Disabling verbose debug for modules",e),o.change.setting("verbose",!1,e,t)}},console:function(e){if(e){if(void 0===b.cache.console)return void o.error(m.console);o.debug("Restoring console function"),t.console=b.cache.console}else o.debug("Disabling console function"),b.cache.console=t.console,t.console={clear:function(){},error:function(){},group:function(){},groupCollapsed:function(){},groupEnd:function(){},info:function(){},log:function(){},markTimeline:function(){},warn:function(){}}},destroy:function(){o.verbose("Destroying previous site for",p),p.removeData(g)},cache:{},setting:function(t,n){if(e.isPlainObject(t))e.extend(!0,d,t);else{if(void 0===n)return d[t];d[t]=n}},internal:function(t,n){if(e.isPlainObject(t))e.extend(!0,o,t);else{if(void 0===n)return o[t];o[t]=n}},debug:function(){d.debug&&(d.performance?o.performance.log(arguments):(o.debug=Function.prototype.bind.call(console.info,console,d.name+":"),o.debug.apply(console,arguments)))},verbose:function(){d.verbose&&d.debug&&(d.performance?o.performance.log(arguments):(o.verbose=Function.prototype.bind.call(console.info,console,d.name+":"),o.verbose.apply(console,arguments)))},error:function(){o.error=Function.prototype.bind.call(console.error,console,d.name+":"),o.error.apply(console,arguments)},performance:{log:function(e){var t,n,i;d.performance&&(t=(new Date).getTime(),i=r||t,n=t-i,r=t,s.push({Element:h,Name:e[0],Arguments:[].slice.call(e,1)||"","Execution Time":n})),clearTimeout(o.performance.timer),o.performance.timer=setTimeout(o.performance.display,500)},display:function(){var t=d.name+":",n=0;r=!1,clearTimeout(o.performance.timer),e.each(s,function(e,t){n+=t["Execution Time"]}),t+=" "+n+"ms",(void 0!==console.group||void 0!==console.table)&&s.length>0&&(console.groupCollapsed(t),console.table?console.table(s):e.each(s,function(e,t){console.log(t.Name+": "+t["Execution Time"]+"ms")}),console.groupEnd()),s=[]}},invoke:function(t,n,i){var r,s,l,c=b;return n=n||u,i=h||i,"string"==typeof t&&void 0!==c&&(t=t.split(/[\. ]/),r=t.length-1,e.each(t,function(n,i){var a=n!=r?i+t[n+1].charAt(0).toUpperCase()+t[n+1].slice(1):t;if(e.isPlainObject(c[a])&&n!=r)c=c[a];else{if(void 0!==c[a])return s=c[a],!1;if(!e.isPlainObject(c[i])||n==r)return void 0!==c[i]?(s=c[i],!1):(o.error(m.method,t),!1);c=c[i]}})),e.isFunction(s)?l=s.apply(i,n):void 0!==s&&(l=s),e.isArray(a)?a.push(l):void 0!==a?a=[a,l]:void 0!==l&&(a=l),s}},c?(void 0===b&&o.initialize(),o.invoke(l)):(void 0!==b&&o.destroy(),o.initialize()),void 0!==a?a:this},e.site.settings={name:"Site",namespace:"site",error:{console:"Console cannot be restored, most likely it was overwritten outside of module",method:"The method you called is not defined."},debug:!1,verbose:!1,performance:!0,modules:["accordion","api","checkbox","dimmer","dropdown","embed","form","modal","nag","popup","rating","shape","sidebar","state","sticky","tab","transition","visit","visibility"],siteNamespace:"site",namespaceStub:{cache:{},config:{},sections:{},section:{},utilities:{}}},e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(n){return!!e.data(n,t)}}):function(t,n,i){return!!e.data(t,i[3])}})}(jQuery,window,document),function(e,t,n,i){"use strict";t=void 0!==t&&t.Math==Math?t:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")(),e.fn.form=function(t){var i,o=e(this),a=o.selector||"",r=(new Date).getTime(),s=[],l=arguments[0],c=arguments[1],u="string"==typeof l,d=[].slice.call(arguments,1);return o.each(function(){var f,m,g,v,p,h,b,y,x,C,w,k,S,T,A,R,P,E,F,O=e(this),D=this,q=[],j=!1;F={initialize:function(){F.get.settings(),u?(void 0===E&&F.instantiate(),F.invoke(l)):(void 0!==E&&E.invoke("destroy"),F.verbose("Initializing form validation",O,y),F.bindEvents(),F.set.defaults(),F.instantiate())},instantiate:function(){F.verbose("Storing instance of module",F),E=F,O.data(R,F)},destroy:function(){F.verbose("Destroying previous module",E),F.removeEvents(),O.removeData(R)},refresh:function(){F.verbose("Refreshing selector cache"),f=O.find(w.field),m=O.find(w.group),g=O.find(w.message),v=O.find(w.prompt),p=O.find(w.submit),h=O.find(w.clear),b=O.find(w.reset)},submit:function(){F.verbose("Submitting form",O),O.submit()},attachEvents:function(t,n){n=n||"submit",e(t).on("click"+P,function(e){F[n](),e.preventDefault()})},bindEvents:function(){F.verbose("Attaching form events"),O.on("submit"+P,F.validate.form).on("blur"+P,w.field,F.event.field.blur).on("click"+P,w.submit,F.submit).on("click"+P,w.reset,F.reset).on("click"+P,w.clear,F.clear),y.keyboardShortcuts&&O.on("keydown"+P,w.field,F.event.field.keydown),f.each(function(){var t=e(this),n=t.prop("type"),i=F.get.changeEvent(n,t);e(this).on(i+P,F.event.field.change)})},clear:function(){f.each(function(){var t=e(this),n=t.parent(),i=t.closest(m),o=i.find(w.prompt),a=t.data(C.defaultValue)||"",r=n.is(w.uiCheckbox),s=n.is(w.uiDropdown);i.hasClass(k.error)&&(F.verbose("Resetting error on field",i),i.removeClass(k.error),o.remove()),s?(F.verbose("Resetting dropdown value",n,a),n.dropdown("clear")):r?t.prop("checked",!1):(F.verbose("Resetting field value",t,a),t.val(""))})},reset:function(){f.each(function(){var t=e(this),n=t.parent(),i=t.closest(m),o=i.find(w.prompt),a=t.data(C.defaultValue),r=n.is(w.uiCheckbox),s=n.is(w.uiDropdown),l=i.hasClass(k.error);void 0!==a&&(l&&(F.verbose("Resetting error on field",i),i.removeClass(k.error),o.remove()),s?(F.verbose("Resetting dropdown value",n,a),n.dropdown("restore defaults")):r?(F.verbose("Resetting checkbox value",n,a),t.prop("checked",a)):(F.verbose("Resetting field value",t,a),t.val(a)))})},determine:{isValid:function(){var t=!0;return e.each(x,function(e,n){F.validate.field(n,e,!0)||(t=!1)}),t}},is:{bracketedRule:function(e){return e.type&&e.type.match(y.regExp.bracket)},shorthandFields:function(e){var t=Object.keys(e),n=e[t[0]];return F.is.shorthandRules(n)},shorthandRules:function(t){return"string"==typeof t||e.isArray(t)},empty:function(e){return!e||0===e.length||(e.is('input[type="checkbox"]')?!e.is(":checked"):F.is.blank(e))},blank:function(t){return""===e.trim(t.val())},valid:function(t){var n=!0;return t?(F.verbose("Checking if field is valid",t),F.validate.field(x[t],t,!1)):(F.verbose("Checking if form is valid"),e.each(x,function(e,t){F.is.valid(e)||(n=!1)}),n)}},removeEvents:function(){O.off(P),f.off(P),p.off(P),f.off(P)},event:{field:{keydown:function(t){var n=e(this),i=t.which,o=n.is(w.input),a=n.is(w.checkbox),r=n.closest(w.uiDropdown).length>0,s={enter:13,escape:27};i==s.escape&&(F.verbose("Escape key pressed blurring field"),n.blur()),t.ctrlKey||i!=s.enter||!o||r||a||(j||(n.one("keyup"+P,F.event.field.keyup),F.submit(),F.debug("Enter pressed on input submitting form")),j=!0)},keyup:function(){j=!1},blur:function(t){var n=e(this),i=n.closest(m),o=F.get.validation(n);i.hasClass(k.error)?(F.debug("Revalidating field",n,o),o&&F.validate.field(o)):"blur"!=y.on&&"change"!=y.on||o&&F.validate.field(o)},change:function(t){var n=e(this),i=n.closest(m),o=F.get.validation(n);o&&("change"==y.on||i.hasClass(k.error)&&y.revalidate)&&(clearTimeout(F.timer),F.timer=setTimeout(function(){F.debug("Revalidating field",n,F.get.validation(n)),F.validate.field(o)},y.delay))}}},get:{ancillaryValue:function(e){return!(!e.type||!e.value&&!F.is.bracketedRule(e))&&(void 0!==e.value?e.value:e.type.match(y.regExp.bracket)[1]+"")},ruleName:function(e){return F.is.bracketedRule(e)?e.type.replace(e.type.match(y.regExp.bracket)[0],""):e.type},changeEvent:function(e,t){return"checkbox"==e||"radio"==e||"hidden"==e||t.is("select")?"change":F.get.inputEvent()},inputEvent:function(){return void 0!==n.createElement("input").oninput?"input":void 0!==n.createElement("input").onpropertychange?"propertychange":"keyup"},fieldsFromShorthand:function(t){var n={};return e.each(t,function(t,i){"string"==typeof i&&(i=[i]),n[t]={rules:[]},e.each(i,function(e,i){n[t].rules.push({type:i})})}),n},prompt:function(e,t){var n,i,o,a=F.get.ruleName(e),r=F.get.ancillaryValue(e),s=e.prompt||y.prompt[a]||y.text.unspecifiedRule,l=-1!==s.search("{value}"),c=-1!==s.search("{name}");return(c||l)&&(i=F.get.field(t.identifier)),l&&(s=s.replace("{value}",i.val())),c&&(n=i.closest(w.group).find("label").eq(0),o=1==n.length?n.text():i.prop("placeholder")||y.text.unspecifiedField,s=s.replace("{name}",o)),s=s.replace("{identifier}",t.identifier),s=s.replace("{ruleValue}",r),e.prompt||F.verbose("Using default validation prompt for type",s,a),s},settings:function(){if(e.isPlainObject(t)){var n=Object.keys(t),i=n.length>0&&(void 0!==t[n[0]].identifier&&void 0!==t[n[0]].rules);i?(y=e.extend(!0,{},e.fn.form.settings,c),x=e.extend({},e.fn.form.settings.defaults,t),F.error(y.error.oldSyntax,D),F.verbose("Extending settings from legacy parameters",x,y)):(t.fields&&F.is.shorthandFields(t.fields)&&(t.fields=F.get.fieldsFromShorthand(t.fields)),y=e.extend(!0,{},e.fn.form.settings,t),x=e.extend({},e.fn.form.settings.defaults,y.fields),F.verbose("Extending settings",x,y))}else y=e.fn.form.settings,x=e.fn.form.settings.defaults,F.verbose("Using default form validation",x,y);A=y.namespace,C=y.metadata,w=y.selector,k=y.className,S=y.regExp,T=y.error,R="module-"+A,P="."+A,E=O.data(R),F.refresh()},field:function(t){return F.verbose("Finding field with identifier",t),t=F.escape.string(t),f.filter("#"+t).length>0?f.filter("#"+t):f.filter('[name="'+t+'"]').length>0?f.filter('[name="'+t+'"]'):f.filter('[name="'+t+'[]"]').length>0?f.filter('[name="'+t+'[]"]'):f.filter("[data-"+C.validate+'="'+t+'"]').length>0?f.filter("[data-"+C.validate+'="'+t+'"]'):e("")},fields:function(t){var n=e();return e.each(t,function(e,t){n=n.add(F.get.field(t))}),n},validation:function(t){var n,i;return!!x&&(e.each(x,function(e,o){i=o.identifier||e,F.get.field(i)[0]==t[0]&&(o.identifier=i,n=o)}),n||!1)},value:function(e){var t,n=[];return n.push(e),t=F.get.values.call(D,n),t[e]},values:function(t){var n=e.isArray(t)?F.get.fields(t):f,i={};return n.each(function(t,n){var o=e(n),a=(o.prop("type"),o.prop("name")),r=o.val(),s=o.is(w.checkbox),l=o.is(w.radio),c=-1!==a.indexOf("[]"),u=!!s&&o.is(":checked");a&&(c?(a=a.replace("[]",""),i[a]||(i[a]=[]),s?u?i[a].push(r||!0):i[a].push(!1):i[a].push(r)):l?void 0===i[a]&&(i[a]=!!u):i[a]=s?!!u&&(r||!0):r)}),i}},has:{field:function(e){return F.verbose("Checking for existence of a field with identifier",e),e=F.escape.string(e),"string"!=typeof e&&F.error(T.identifier,e),f.filter("#"+e).length>0||(f.filter('[name="'+e+'"]').length>0||f.filter("[data-"+C.validate+'="'+e+'"]').length>0)}},escape:{string:function(e){return e=String(e),e.replace(S.escape,"\\$&")}},add:{rule:function(e,t){F.add.field(e,t)},field:function(t,n){var i={};F.is.shorthandRules(n)?(n=e.isArray(n)?n:[n],i[t]={rules:[]},e.each(n,function(e,n){i[t].rules.push({type:n})})):i[t]=n,x=e.extend({},x,i),F.debug("Adding rules",i,x)},fields:function(t){var n;n=t&&F.is.shorthandFields(t)?F.get.fieldsFromShorthand(t):t,x=e.extend({},x,n)},prompt:function(t,n){var i=F.get.field(t),o=i.closest(m),a=o.children(w.prompt),r=0!==a.length;n="string"==typeof n?[n]:n,F.verbose("Adding field error state",t),o.addClass(k.error),y.inline&&(r||(a=y.templates.prompt(n),a.appendTo(o)),a.html(n[0]),r?F.verbose("Inline errors are disabled, no inline error added",t):y.transition&&void 0!==e.fn.transition&&O.transition("is supported")?(F.verbose("Displaying error with css transition",y.transition),a.transition(y.transition+" in",y.duration)):(F.verbose("Displaying error with fallback javascript animation"),a.fadeIn(y.duration)))},errors:function(e){F.debug("Adding form error messages",e),F.set.error(),g.html(y.templates.error(e))}},remove:{rule:function(t,n){var i=e.isArray(n)?n:[n];if(void 0==n)return F.debug("Removed all rules"),void(x[t].rules=[]);void 0!=x[t]&&e.isArray(x[t].rules)&&e.each(x[t].rules,function(e,n){-1!==i.indexOf(n.type)&&(F.debug("Removed rule",n.type),x[t].rules.splice(e,1))})},field:function(t){var n=e.isArray(t)?t:[t];e.each(n,function(e,t){F.remove.rule(t)})},rules:function(t,n){e.isArray(t)?e.each(fields,function(e,t){F.remove.rule(t,n)}):F.remove.rule(t,n)},fields:function(e){F.remove.field(e)},prompt:function(t){var n=F.get.field(t),i=n.closest(m),o=i.children(w.prompt);i.removeClass(k.error),y.inline&&o.is(":visible")&&(F.verbose("Removing prompt for field",t),y.transition&&void 0!==e.fn.transition&&O.transition("is supported")?o.transition(y.transition+" out",y.duration,function(){o.remove()}):o.fadeOut(y.duration,function(){o.remove()}))}},set:{success:function(){O.removeClass(k.error).addClass(k.success)},defaults:function(){f.each(function(){var t=e(this),n=t.filter(w.checkbox).length>0,i=n?t.is(":checked"):t.val();t.data(C.defaultValue,i)})},error:function(){O.removeClass(k.success).addClass(k.error)},value:function(e,t){var n={};return n[e]=t,F.set.values.call(D,n)},values:function(t){e.isEmptyObject(t)||e.each(t,function(t,n){var i,o=F.get.field(t),a=o.parent(),r=e.isArray(n),s=a.is(w.uiCheckbox),l=a.is(w.uiDropdown),c=o.is(w.radio)&&s,u=o.length>0;u&&(r&&s?(F.verbose("Selecting multiple",n,o),a.checkbox("uncheck"),e.each(n,function(e,t){i=o.filter('[value="'+t+'"]'),a=i.parent(),i.length>0&&a.checkbox("check")})):c?(F.verbose("Selecting radio value",n,o),o.filter('[value="'+n+'"]').parent(w.uiCheckbox).checkbox("check")):s?(F.verbose("Setting checkbox value",n,a),!0===n?a.checkbox("check"):a.checkbox("uncheck")):l?(F.verbose("Setting dropdown value",n,a),a.dropdown("set selected",n)):(F.verbose("Setting field value",n,o),o.val(n)))})}},validate:{form:function(e,t){var n=F.get.values();if(j)return!1;if(q=[],F.determine.isValid()){if(F.debug("Form has no validation errors, submitting"),F.set.success(),!0!==t)return y.onSuccess.call(D,e,n)}else if(F.debug("Form has errors"),F.set.error(),y.inline||F.add.errors(q),void 0!==O.data("moduleApi")&&e.stopImmediatePropagation(),!0!==t)return y.onFailure.call(D,q,n)},field:function(t,n,i){i=void 0===i||i,"string"==typeof t&&(F.verbose("Validating field",t),n=t,t=x[t]);var o=t.identifier||n,a=F.get.field(o),r=!!t.depends&&F.get.field(t.depends),s=!0,l=[];return t.identifier||(F.debug("Using field name as identifier",o),t.identifier=o),a.prop("disabled")?(F.debug("Field is disabled. Skipping",o),s=!0):t.optional&&F.is.blank(a)?(F.debug("Field is optional and blank. Skipping",o),s=!0):t.depends&&F.is.empty(r)?(F.debug("Field depends on another value that is not present or empty. Skipping",r),s=!0):void 0!==t.rules&&e.each(t.rules,function(e,n){F.has.field(o)&&!F.validate.rule(t,n)&&(F.debug("Field is invalid",o,n.type),l.push(F.get.prompt(n,t)),s=!1)}),s?(i&&(F.remove.prompt(o,l),y.onValid.call(a)),!0):(i&&(q=q.concat(l),F.add.prompt(o,l),y.onInvalid.call(a,l)),!1)},rule:function(t,n){var i=F.get.field(t.identifier),o=(n.type,i.val()),a=F.get.ancillaryValue(n),r=F.get.ruleName(n),s=y.rules[r];return e.isFunction(s)?(o=void 0===o||""===o||null===o?"":e.trim(o+""),s.call(i,o,a)):void F.error(T.noRule,r)}},setting:function(t,n){if(e.isPlainObject(t))e.extend(!0,y,t);else{if(void 0===n)return y[t];y[t]=n}},internal:function(t,n){if(e.isPlainObject(t))e.extend(!0,F,t);else{if(void 0===n)return F[t];F[t]=n}},debug:function(){!y.silent&&y.debug&&(y.performance?F.performance.log(arguments):(F.debug=Function.prototype.bind.call(console.info,console,y.name+":"),F.debug.apply(console,arguments)))},verbose:function(){!y.silent&&y.verbose&&y.debug&&(y.performance?F.performance.log(arguments):(F.verbose=Function.prototype.bind.call(console.info,console,y.name+":"),F.verbose.apply(console,arguments)))},error:function(){y.silent||(F.error=Function.prototype.bind.call(console.error,console,y.name+":"),F.error.apply(console,arguments))},performance:{log:function(e){var t,n,i;y.performance&&(t=(new Date).getTime(),i=r||t,n=t-i,r=t,s.push({Name:e[0],Arguments:[].slice.call(e,1)||"",Element:D,"Execution Time":n})),clearTimeout(F.performance.timer),F.performance.timer=setTimeout(F.performance.display,500)},display:function(){var t=y.name+":",n=0;r=!1,clearTimeout(F.performance.timer),e.each(s,function(e,t){n+=t["Execution Time"]}),t+=" "+n+"ms",a&&(t+=" '"+a+"'"),o.length>1&&(t+=" ("+o.length+")"),(void 0!==console.group||void 0!==console.table)&&s.length>0&&(console.groupCollapsed(t),console.table?console.table(s):e.each(s,function(e,t){console.log(t.Name+": "+t["Execution Time"]+"ms")}),console.groupEnd()),s=[]}},invoke:function(t,n,o){var a,r,s,l=E;return n=n||d,o=D||o,"string"==typeof t&&void 0!==l&&(t=t.split(/[\. ]/),a=t.length-1,e.each(t,function(n,i){var o=n!=a?i+t[n+1].charAt(0).toUpperCase()+t[n+1].slice(1):t;if(e.isPlainObject(l[o])&&n!=a)l=l[o];else{if(void 0!==l[o])return r=l[o],!1;if(!e.isPlainObject(l[i])||n==a)return void 0!==l[i]&&(r=l[i],!1);l=l[i]}})),e.isFunction(r)?s=r.apply(o,n):void 0!==r&&(s=r),e.isArray(i)?i.push(s):void 0!==i?i=[i,s]:void 0!==s&&(i=s),r}},F.initialize()}),void 0!==i?i:this},e.fn.form.settings={name:"Form",namespace:"form",debug:!1,verbose:!1,performance:!0,fields:!1,keyboardShortcuts:!0,on:"submit",inline:!1,delay:200,revalidate:!0,transition:"scale",duration:200,onValid:function(){},onInvalid:function(){},onSuccess:function(){return!0},onFailure:function(){return!1},metadata:{defaultValue:"default",validate:"validate"},regExp:{htmlID:/^[a-zA-Z][\w:.-]*$/g,bracket:/\[(.*)\]/i,decimal:/^\d+\.?\d*$/,email:/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,escape:/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,flags:/^\/(.*)\/(.*)?/,integer:/^\-?\d+$/,number:/^\-?\d*(\.\d+)?$/,url:/(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/i},text:{unspecifiedRule:"Please enter a valid value",unspecifiedField:"This field"},prompt:{empty:"{name} must have a value",checked:"{name} must be checked",email:"{name} must be a valid e-mail",url:"{name} must be a valid url",regExp:"{name} is not formatted correctly",integer:"{name} must be an integer",decimal:"{name} must be a decimal number",number:"{name} must be set to a number",is:'{name} must be "{ruleValue}"',isExactly:'{name} must be exactly "{ruleValue}"',not:'{name} cannot be set to "{ruleValue}"',notExactly:'{name} cannot be set to exactly "{ruleValue}"',contain:'{name} cannot contain "{ruleValue}"',containExactly:'{name} cannot contain exactly "{ruleValue}"',doesntContain:'{name} must contain "{ruleValue}"',doesntContainExactly:'{name} must contain exactly "{ruleValue}"',minLength:"{name} must be at least {ruleValue} characters",length:"{name} must be at least {ruleValue} characters",exactLength:"{name} must be exactly {ruleValue} characters",maxLength:"{name} cannot be longer than {ruleValue} characters",match:"{name} must match {ruleValue} field",different:"{name} must have a different value than {ruleValue} field",creditCard:"{name} must be a valid credit card number",minCount:"{name} must have at least {ruleValue} choices",exactCount:"{name} must have exactly {ruleValue} choices",maxCount:"{name} must have {ruleValue} or less choices"},selector:{checkbox:'input[type="checkbox"], input[type="radio"]',clear:".clear",field:"input, textarea, select",group:".field",input:"input",message:".error.message",prompt:".prompt.label",radio:'input[type="radio"]',reset:'.reset:not([type="reset"])',submit:'.submit:not([type="submit"])',uiCheckbox:".ui.checkbox",uiDropdown:".ui.dropdown"},className:{error:"error",label:"ui prompt label",pressed:"down",success:"success"},error:{identifier:"You must specify a string identifier for each field",method:"The method you called is not defined.",noRule:"There is no rule matching the one you specified",oldSyntax:"Starting in 2.0 forms now only take a single settings object. Validation settings converted to new syntax automatically."},templates:{error:function(t){var n='
    ';return e.each(t,function(e,t){n+="
  • "+t+"
  • "}),n+="
",e(n)},prompt:function(t){return e("
").addClass("ui basic red pointing prompt label").html(t[0])}},rules:{empty:function(t){return!(void 0===t||""===t||e.isArray(t)&&0===t.length)},checked:function(){return e(this).filter(":checked").length>0},email:function(t){return e.fn.form.settings.regExp.email.test(t)},url:function(t){return e.fn.form.settings.regExp.url.test(t)},regExp:function(t,n){if(n instanceof RegExp)return t.match(n);var i,o=n.match(e.fn.form.settings.regExp.flags);return o&&(n=o.length>=2?o[1]:n,i=o.length>=3?o[2]:""),t.match(new RegExp(n,i))},integer:function(t,n){var i,o,a,r=e.fn.form.settings.regExp.integer;return n&&-1===["",".."].indexOf(n)&&(-1==n.indexOf("..")?r.test(n)&&(i=o=n-0):(a=n.split("..",2),r.test(a[0])&&(i=a[0]-0),r.test(a[1])&&(o=a[1]-0))),r.test(t)&&(void 0===i||t>=i)&&(void 0===o||t<=o)},decimal:function(t){return e.fn.form.settings.regExp.decimal.test(t)},number:function(t){return e.fn.form.settings.regExp.number.test(t)},is:function(e,t){return t="string"==typeof t?t.toLowerCase():t,(e="string"==typeof e?e.toLowerCase():e)==t},isExactly:function(e,t){return e==t},not:function(e,t){return e="string"==typeof e?e.toLowerCase():e,t="string"==typeof t?t.toLowerCase():t,e!=t},notExactly:function(e,t){return e!=t},contains:function(t,n){return n=n.replace(e.fn.form.settings.regExp.escape,"\\$&"),-1!==t.search(new RegExp(n,"i"))},containsExactly:function(t,n){return n=n.replace(e.fn.form.settings.regExp.escape,"\\$&"),-1!==t.search(new RegExp(n))},doesntContain:function(t,n){return n=n.replace(e.fn.form.settings.regExp.escape,"\\$&"),-1===t.search(new RegExp(n,"i"))},doesntContainExactly:function(t,n){return n=n.replace(e.fn.form.settings.regExp.escape,"\\$&"),-1===t.search(new RegExp(n))},minLength:function(e,t){return void 0!==e&&e.length>=t},length:function(e,t){return void 0!==e&&e.length>=t},exactLength:function(e,t){return void 0!==e&&e.length==t},maxLength:function(e,t){return void 0!==e&&e.length<=t},match:function(t,n){var i;e(this);return e('[data-validate="'+n+'"]').length>0?i=e('[data-validate="'+n+'"]').val():e("#"+n).length>0?i=e("#"+n).val():e('[name="'+n+'"]').length>0?i=e('[name="'+n+'"]').val():e('[name="'+n+'[]"]').length>0&&(i=e('[name="'+n+'[]"]')),void 0!==i&&t.toString()==i.toString()},different:function(t,n){var i;e(this);return e('[data-validate="'+n+'"]').length>0?i=e('[data-validate="'+n+'"]').val():e("#"+n).length>0?i=e("#"+n).val():e('[name="'+n+'"]').length>0?i=e('[name="'+n+'"]').val():e('[name="'+n+'[]"]').length>0&&(i=e('[name="'+n+'[]"]')),void 0!==i&&t.toString()!==i.toString()},creditCard:function(t,n){var i,o,a={visa:{pattern:/^4/,length:[16]},amex:{pattern:/^3[47]/,length:[15]},mastercard:{pattern:/^5[1-5]/,length:[16]},discover:{pattern:/^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)/,length:[16]},unionPay:{pattern:/^(62|88)/,length:[16,17,18,19]},jcb:{pattern:/^35(2[89]|[3-8][0-9])/,length:[16]},maestro:{pattern:/^(5018|5020|5038|6304|6759|676[1-3])/,length:[12,13,14,15,16,17,18,19]},dinersClub:{pattern:/^(30[0-5]|^36)/,length:[14]},laser:{pattern:/^(6304|670[69]|6771)/,length:[16,17,18,19]},visaElectron:{pattern:/^(4026|417500|4508|4844|491(3|7))/,length:[16]}},r={},s=!1,l="string"==typeof n&&n.split(",");if("string"==typeof t&&0!==t.length){if(t=t.replace(/[\-]/g,""),l&&(e.each(l,function(n,i){(o=a[i])&&(r={length:-1!==e.inArray(t.length,o.length),pattern:-1!==t.search(o.pattern)},r.length&&r.pattern&&(s=!0))}),!s))return!1;if(i={number:-1!==e.inArray(t.length,a.unionPay.length),pattern:-1!==t.search(a.unionPay.pattern)},i.number&&i.pattern)return!0;for(var c=t.length,u=0,d=[[0,1,2,3,4,5,6,7,8,9],[0,2,4,6,8,1,3,5,7,9]],f=0;c--;)f+=d[u][parseInt(t.charAt(c),10)],u^=1;return f%10==0&&f>0}},minCount:function(e,t){return 0==t||(1==t?""!==e:e.split(",").length>=t)},exactCount:function(e,t){return 0==t?""===e:1==t?""!==e&&-1===e.search(","):e.split(",").length==t},maxCount:function(e,t){return 0!=t&&(1==t?-1===e.search(","):e.split(",").length<=t)}}}}(jQuery,window,document),function(e,t,n,i){"use strict";t=void 0!==t&&t.Math==Math?t:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")(),e.fn.accordion=function(n){var i,o=e(this),a=(new Date).getTime(),r=[],s=arguments[0],l="string"==typeof s,c=[].slice.call(arguments,1);t.requestAnimationFrame||t.mozRequestAnimationFrame||t.webkitRequestAnimationFrame||t.msRequestAnimationFrame;return o.each(function(){var u,d,f=e.isPlainObject(n)?e.extend(!0,{},e.fn.accordion.settings,n):e.extend({},e.fn.accordion.settings),m=f.className,g=f.namespace,v=f.selector,p=f.error,h="."+g,b="module-"+g,y=o.selector||"",x=e(this),C=x.find(v.title),w=x.find(v.content),k=this,S=x.data(b);d={initialize:function(){d.debug("Initializing",x),d.bind.events(),f.observeChanges&&d.observeChanges(),d.instantiate()},instantiate:function(){S=d,x.data(b,d)},destroy:function(){d.debug("Destroying previous instance",x),x.off(h).removeData(b)},refresh:function(){C=x.find(v.title),w=x.find(v.content)},observeChanges:function(){"MutationObserver"in t&&(u=new MutationObserver(function(e){d.debug("DOM tree modified, updating selector cache"),d.refresh()}),u.observe(k,{childList:!0,subtree:!0}),d.debug("Setting up mutation observer",u))},bind:{events:function(){d.debug("Binding delegated events"),x.on(f.on+h,v.trigger,d.event.click)}},event:{click:function(){d.toggle.call(this)}},toggle:function(t){var n=void 0!==t?"number"==typeof t?C.eq(t):e(t).closest(v.title):e(this).closest(v.title),i=n.next(w),o=i.hasClass(m.animating),a=i.hasClass(m.active),r=a&&!o,s=!a&&o;d.debug("Toggling visibility of content",n),r||s?f.collapsible?d.close.call(n):d.debug("Cannot close accordion content collapsing is disabled"):d.open.call(n)},open:function(t){var n=void 0!==t?"number"==typeof t?C.eq(t):e(t).closest(v.title):e(this).closest(v.title),i=n.next(w),o=i.hasClass(m.animating);if(i.hasClass(m.active)||o)return void d.debug("Accordion already open, skipping",i);d.debug("Opening accordion content",n),f.onOpening.call(i),f.exclusive&&d.closeOthers.call(n),n.addClass(m.active),i.stop(!0,!0).addClass(m.animating),f.animateChildren&&(void 0!==e.fn.transition&&x.transition("is supported")?i.children().transition({animation:"fade in",queue:!1,useFailSafe:!0,debug:f.debug,verbose:f.verbose,duration:f.duration}):i.children().stop(!0,!0).animate({opacity:1},f.duration,d.resetOpacity)),i.slideDown(f.duration,f.easing,function(){i.removeClass(m.animating).addClass(m.active),d.reset.display.call(this),f.onOpen.call(this),f.onChange.call(this)})},close:function(t){var n=void 0!==t?"number"==typeof t?C.eq(t):e(t).closest(v.title):e(this).closest(v.title),i=n.next(w),o=i.hasClass(m.animating),a=i.hasClass(m.active),r=!a&&o,s=a&&o;!a&&!r||s||(d.debug("Closing accordion content",i),f.onClosing.call(i),n.removeClass(m.active),i.stop(!0,!0).addClass(m.animating),f.animateChildren&&(void 0!==e.fn.transition&&x.transition("is supported")?i.children().transition({animation:"fade out",queue:!1,useFailSafe:!0,debug:f.debug,verbose:f.verbose,duration:f.duration}):i.children().stop(!0,!0).animate({opacity:0},f.duration,d.resetOpacity)),i.slideUp(f.duration,f.easing,function(){i.removeClass(m.animating).removeClass(m.active),d.reset.display.call(this),f.onClose.call(this),f.onChange.call(this)}))},closeOthers:function(t){var n,i,o,a=void 0!==t?C.eq(t):e(this).closest(v.title),r=a.parents(v.content).prev(v.title),s=a.closest(v.accordion),l=v.title+"."+m.active+":visible",c=v.content+"."+m.active+":visible";f.closeNested?(n=s.find(l).not(r),o=n.next(w)):(n=s.find(l).not(r),i=s.find(c).find(l).not(r),n=n.not(i),o=n.next(w)),n.length>0&&(d.debug("Exclusive enabled, closing other content",n),n.removeClass(m.active),o.removeClass(m.animating).stop(!0,!0),f.animateChildren&&(void 0!==e.fn.transition&&x.transition("is supported")?o.children().transition({animation:"fade out",useFailSafe:!0,debug:f.debug,verbose:f.verbose,duration:f.duration}):o.children().stop(!0,!0).animate({opacity:0},f.duration,d.resetOpacity)),o.slideUp(f.duration,f.easing,function(){e(this).removeClass(m.active),d.reset.display.call(this)}))},reset:{display:function(){d.verbose("Removing inline display from element",this),e(this).css("display",""),""===e(this).attr("style")&&e(this).attr("style","").removeAttr("style")},opacity:function(){d.verbose("Removing inline opacity from element",this),e(this).css("opacity",""),""===e(this).attr("style")&&e(this).attr("style","").removeAttr("style")}},setting:function(t,n){if(d.debug("Changing setting",t,n),e.isPlainObject(t))e.extend(!0,f,t);else{if(void 0===n)return f[t];e.isPlainObject(f[t])?e.extend(!0,f[t],n):f[t]=n}},internal:function(t,n){if(d.debug("Changing internal",t,n),void 0===n)return d[t];e.isPlainObject(t)?e.extend(!0,d,t):d[t]=n},debug:function(){!f.silent&&f.debug&&(f.performance?d.performance.log(arguments):(d.debug=Function.prototype.bind.call(console.info,console,f.name+":"),d.debug.apply(console,arguments)))},verbose:function(){ +!f.silent&&f.verbose&&f.debug&&(f.performance?d.performance.log(arguments):(d.verbose=Function.prototype.bind.call(console.info,console,f.name+":"),d.verbose.apply(console,arguments)))},error:function(){f.silent||(d.error=Function.prototype.bind.call(console.error,console,f.name+":"),d.error.apply(console,arguments))},performance:{log:function(e){var t,n,i;f.performance&&(t=(new Date).getTime(),i=a||t,n=t-i,a=t,r.push({Name:e[0],Arguments:[].slice.call(e,1)||"",Element:k,"Execution Time":n})),clearTimeout(d.performance.timer),d.performance.timer=setTimeout(d.performance.display,500)},display:function(){var t=f.name+":",n=0;a=!1,clearTimeout(d.performance.timer),e.each(r,function(e,t){n+=t["Execution Time"]}),t+=" "+n+"ms",y&&(t+=" '"+y+"'"),(void 0!==console.group||void 0!==console.table)&&r.length>0&&(console.groupCollapsed(t),console.table?console.table(r):e.each(r,function(e,t){console.log(t.Name+": "+t["Execution Time"]+"ms")}),console.groupEnd()),r=[]}},invoke:function(t,n,o){var a,r,s,l=S;return n=n||c,o=k||o,"string"==typeof t&&void 0!==l&&(t=t.split(/[\. ]/),a=t.length-1,e.each(t,function(n,i){var o=n!=a?i+t[n+1].charAt(0).toUpperCase()+t[n+1].slice(1):t;if(e.isPlainObject(l[o])&&n!=a)l=l[o];else{if(void 0!==l[o])return r=l[o],!1;if(!e.isPlainObject(l[i])||n==a)return void 0!==l[i]?(r=l[i],!1):(d.error(p.method,t),!1);l=l[i]}})),e.isFunction(r)?s=r.apply(o,n):void 0!==r&&(s=r),e.isArray(i)?i.push(s):void 0!==i?i=[i,s]:void 0!==s&&(i=s),r}},l?(void 0===S&&d.initialize(),d.invoke(s)):(void 0!==S&&S.invoke("destroy"),d.initialize())}),void 0!==i?i:this},e.fn.accordion.settings={name:"Accordion",namespace:"accordion",silent:!1,debug:!1,verbose:!1,performance:!0,on:"click",observeChanges:!0,exclusive:!0,collapsible:!0,closeNested:!1,animateChildren:!0,duration:350,easing:"easeOutQuad",onOpening:function(){},onOpen:function(){},onClosing:function(){},onClose:function(){},onChange:function(){},error:{method:"The method you called is not defined"},className:{active:"active",animating:"animating"},selector:{accordion:".accordion",title:".title",trigger:".title",content:".content"}},e.extend(e.easing,{easeOutQuad:function(e,t,n,i,o){return-i*(t/=o)*(t-2)+n}})}(jQuery,window,document),function(e,t,n,i){"use strict";t=void 0!==t&&t.Math==Math?t:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")(),e.fn.checkbox=function(i){var o,a=e(this),r=a.selector||"",s=(new Date).getTime(),l=[],c=arguments[0],u="string"==typeof c,d=[].slice.call(arguments,1);return a.each(function(){var a,f,m=e.extend(!0,{},e.fn.checkbox.settings,i),g=m.className,v=m.namespace,p=m.selector,h=m.error,b="."+v,y="module-"+v,x=e(this),C=e(this).children(p.label),w=e(this).children(p.input),k=w[0],S=!1,T=!1,A=x.data(y),R=this;f={initialize:function(){f.verbose("Initializing checkbox",m),f.create.label(),f.bind.events(),f.set.tabbable(),f.hide.input(),f.observeChanges(),f.instantiate(),f.setup()},instantiate:function(){f.verbose("Storing instance of module",f),A=f,x.data(y,f)},destroy:function(){f.verbose("Destroying module"),f.unbind.events(),f.show.input(),x.removeData(y)},fix:{reference:function(){x.is(p.input)&&(f.debug("Behavior called on adjusting invoked element"),x=x.closest(p.checkbox),f.refresh())}},setup:function(){f.set.initialLoad(),f.is.indeterminate()?(f.debug("Initial value is indeterminate"),f.indeterminate()):f.is.checked()?(f.debug("Initial value is checked"),f.check()):(f.debug("Initial value is unchecked"),f.uncheck()),f.remove.initialLoad()},refresh:function(){C=x.children(p.label),w=x.children(p.input),k=w[0]},hide:{input:function(){f.verbose("Modifying z-index to be unselectable"),w.addClass(g.hidden)}},show:{input:function(){f.verbose("Modifying z-index to be selectable"),w.removeClass(g.hidden)}},observeChanges:function(){"MutationObserver"in t&&(a=new MutationObserver(function(e){f.debug("DOM tree modified, updating selector cache"),f.refresh()}),a.observe(R,{childList:!0,subtree:!0}),f.debug("Setting up mutation observer",a))},attachEvents:function(t,n){var i=e(t);n=e.isFunction(f[n])?f[n]:f.toggle,i.length>0?(f.debug("Attaching checkbox events to element",t,n),i.on("click"+b,n)):f.error(h.notFound)},event:{click:function(t){var n=e(t.target);return n.is(p.input)?void f.verbose("Using default check action on initialized checkbox"):n.is(p.link)?void f.debug("Clicking link inside checkbox, skipping toggle"):(f.toggle(),w.focus(),void t.preventDefault())},keydown:function(e){var t=e.which,n={enter:13,space:32,escape:27};t==n.escape?(f.verbose("Escape key pressed blurring field"),w.blur(),T=!0):e.ctrlKey||t!=n.space&&t!=n.enter?T=!1:(f.verbose("Enter/space key pressed, toggling checkbox"),f.toggle(),T=!0)},keyup:function(e){T&&e.preventDefault()}},check:function(){f.should.allowCheck()&&(f.debug("Checking checkbox",w),f.set.checked(),f.should.ignoreCallbacks()||(m.onChecked.call(k),m.onChange.call(k)))},uncheck:function(){f.should.allowUncheck()&&(f.debug("Unchecking checkbox"),f.set.unchecked(),f.should.ignoreCallbacks()||(m.onUnchecked.call(k),m.onChange.call(k)))},indeterminate:function(){if(f.should.allowIndeterminate())return void f.debug("Checkbox is already indeterminate");f.debug("Making checkbox indeterminate"),f.set.indeterminate(),f.should.ignoreCallbacks()||(m.onIndeterminate.call(k),m.onChange.call(k))},determinate:function(){if(f.should.allowDeterminate())return void f.debug("Checkbox is already determinate");f.debug("Making checkbox determinate"),f.set.determinate(),f.should.ignoreCallbacks()||(m.onDeterminate.call(k),m.onChange.call(k))},enable:function(){if(f.is.enabled())return void f.debug("Checkbox is already enabled");f.debug("Enabling checkbox"),f.set.enabled(),m.onEnable.call(k),m.onEnabled.call(k)},disable:function(){if(f.is.disabled())return void f.debug("Checkbox is already disabled");f.debug("Disabling checkbox"),f.set.disabled(),m.onDisable.call(k),m.onDisabled.call(k)},get:{radios:function(){var t=f.get.name();return e('input[name="'+t+'"]').closest(p.checkbox)},otherRadios:function(){return f.get.radios().not(x)},name:function(){return w.attr("name")}},is:{initialLoad:function(){return S},radio:function(){return w.hasClass(g.radio)||"radio"==w.attr("type")},indeterminate:function(){return void 0!==w.prop("indeterminate")&&w.prop("indeterminate")},checked:function(){return void 0!==w.prop("checked")&&w.prop("checked")},disabled:function(){return void 0!==w.prop("disabled")&&w.prop("disabled")},enabled:function(){return!f.is.disabled()},determinate:function(){return!f.is.indeterminate()},unchecked:function(){return!f.is.checked()}},should:{allowCheck:function(){return f.is.determinate()&&f.is.checked()&&!f.should.forceCallbacks()?(f.debug("Should not allow check, checkbox is already checked"),!1):!1!==m.beforeChecked.apply(k)||(f.debug("Should not allow check, beforeChecked cancelled"),!1)},allowUncheck:function(){return f.is.determinate()&&f.is.unchecked()&&!f.should.forceCallbacks()?(f.debug("Should not allow uncheck, checkbox is already unchecked"),!1):!1!==m.beforeUnchecked.apply(k)||(f.debug("Should not allow uncheck, beforeUnchecked cancelled"),!1)},allowIndeterminate:function(){return f.is.indeterminate()&&!f.should.forceCallbacks()?(f.debug("Should not allow indeterminate, checkbox is already indeterminate"),!1):!1!==m.beforeIndeterminate.apply(k)||(f.debug("Should not allow indeterminate, beforeIndeterminate cancelled"),!1)},allowDeterminate:function(){return f.is.determinate()&&!f.should.forceCallbacks()?(f.debug("Should not allow determinate, checkbox is already determinate"),!1):!1!==m.beforeDeterminate.apply(k)||(f.debug("Should not allow determinate, beforeDeterminate cancelled"),!1)},forceCallbacks:function(){return f.is.initialLoad()&&m.fireOnInit},ignoreCallbacks:function(){return S&&!m.fireOnInit}},can:{change:function(){return!(x.hasClass(g.disabled)||x.hasClass(g.readOnly)||w.prop("disabled")||w.prop("readonly"))},uncheck:function(){return"boolean"==typeof m.uncheckable?m.uncheckable:!f.is.radio()}},set:{initialLoad:function(){S=!0},checked:function(){if(f.verbose("Setting class to checked"),x.removeClass(g.indeterminate).addClass(g.checked),f.is.radio()&&f.uncheckOthers(),!f.is.indeterminate()&&f.is.checked())return void f.debug("Input is already checked, skipping input property change");f.verbose("Setting state to checked",k),w.prop("indeterminate",!1).prop("checked",!0),f.trigger.change()},unchecked:function(){if(f.verbose("Removing checked class"),x.removeClass(g.indeterminate).removeClass(g.checked),!f.is.indeterminate()&&f.is.unchecked())return void f.debug("Input is already unchecked");f.debug("Setting state to unchecked"),w.prop("indeterminate",!1).prop("checked",!1),f.trigger.change()},indeterminate:function(){if(f.verbose("Setting class to indeterminate"),x.addClass(g.indeterminate),f.is.indeterminate())return void f.debug("Input is already indeterminate, skipping input property change");f.debug("Setting state to indeterminate"),w.prop("indeterminate",!0),f.trigger.change()},determinate:function(){if(f.verbose("Removing indeterminate class"),x.removeClass(g.indeterminate),f.is.determinate())return void f.debug("Input is already determinate, skipping input property change");f.debug("Setting state to determinate"),w.prop("indeterminate",!1)},disabled:function(){if(f.verbose("Setting class to disabled"),x.addClass(g.disabled),f.is.disabled())return void f.debug("Input is already disabled, skipping input property change");f.debug("Setting state to disabled"),w.prop("disabled","disabled"),f.trigger.change()},enabled:function(){if(f.verbose("Removing disabled class"),x.removeClass(g.disabled),f.is.enabled())return void f.debug("Input is already enabled, skipping input property change");f.debug("Setting state to enabled"),w.prop("disabled",!1),f.trigger.change()},tabbable:function(){f.verbose("Adding tabindex to checkbox"),void 0===w.attr("tabindex")&&w.attr("tabindex",0)}},remove:{initialLoad:function(){S=!1}},trigger:{change:function(){var e=n.createEvent("HTMLEvents"),t=w[0];t&&(f.verbose("Triggering native change event"),e.initEvent("change",!0,!1),t.dispatchEvent(e))}},create:{label:function(){w.prevAll(p.label).length>0?(w.prev(p.label).detach().insertAfter(w),f.debug("Moving existing label",C)):f.has.label()||(C=e("