Merge branch 'master' into i3725-refactor-send-component

This commit is contained in:
Dan 2018-04-24 13:31:39 -02:30
commit 02a6d2089e
132 changed files with 5992 additions and 2199 deletions

View File

@ -19,15 +19,6 @@ workflows:
requires:
- prep-deps-npm
- prep-build
- job-screens:
requires:
- prep-deps-npm
- prep-build
- job-publish:
requires:
- prep-deps-npm
- prep-build
- job-screens
- test-unit:
requires:
- prep-deps-npm
@ -54,11 +45,21 @@ workflows:
- test-lint
- test-unit
- test-e2e
- job-screens
- test-integration-mascara-chrome
- test-integration-mascara-firefox
- test-integration-flat-chrome
- test-integration-flat-firefox
- job-screens:
requires:
- prep-deps-npm
- prep-build
- all-tests-pass
- job-publish:
requires:
- prep-deps-npm
- prep-build
- job-screens
- all-tests-pass
jobs:
prep-deps-npm:

View File

@ -35,7 +35,6 @@
"globals": {
"document": false,
"log": true,
"navigator": false,
"web3": true,
"window": false

1
.gitignore vendored
View File

@ -20,6 +20,7 @@ dist
builds/
disc/
builds.zip
docs/jsdocs
development/bundle.js
development/states.js

View File

@ -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
}
}

View File

@ -2,7 +2,11 @@
## 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

View File

@ -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)

View File

@ -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"
}
}

View File

@ -908,5 +908,8 @@
},
"youSign": {
"message": "You are signing"
},
"generatingTransaction": {
"message": "Generating transaction"
}
}

View File

@ -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": "सेटिंग्स"

View File

@ -1,4 +1,5 @@
[
{ "code": "cs", "name": "Czech" },
{ "code": "de", "name": "German" },
{ "code": "en", "name": "English" },
{ "code": "es", "name": "Spanish" },
@ -13,6 +14,7 @@
{ "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" },

View File

@ -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": "நீங்கள் கையெழுத்திடுகிறீர்கள்"
}
}

View File

@ -1,3 +1,7 @@
/**
* @file The entry point for the web extension singleton process.
*/
const urlUtil = require('url')
const endOfStream = require('end-of-stream')
const pump = require('pump')
@ -21,12 +25,16 @@ const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
const EdgeEncryptor = require('./edge-encryptor')
const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code')
const getObjStructure = require('./lib/getObjStructure')
const {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_FULLSCREEN,
} = require('./lib/enums')
const STORAGE_KEY = 'metamask-config'
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
window.log = log
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
const platform = new ExtensionPlatform()
const notificationManager = new NotificationManager()
@ -44,7 +52,7 @@ const isEdge = !isIE && !!window.StyleMedia
let popupIsOpen = false
let notificationIsOpen = false
let openMetamaskTabsIDs = {}
const openMetamaskTabsIDs = {}
// state persistence
const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY })
@ -57,6 +65,90 @@ initialize().catch(log.error)
// setup metamask mesh testing container
setupMetamaskMeshMetrics()
/**
* An object representing a transaction, in whatever state it is in.
* @typedef TransactionMeta
*
* @property {number} id - An internally unique tx identifier.
* @property {number} time - Time the tx was first suggested, in unix epoch time (ms).
* @property {string} status - The current transaction status (unapproved, signed, submitted, dropped, failed, rejected), as defined in `tx-state-manager.js`.
* @property {string} metamaskNetworkId - The transaction's network ID, used for EIP-155 compliance.
* @property {boolean} loadingDefaults - TODO: Document
* @property {Object} txParams - The tx params as passed to the network provider.
* @property {Object[]} history - A history of mutations to this TransactionMeta object.
* @property {boolean} gasPriceSpecified - True if the suggesting dapp specified a gas price, prevents auto-estimation.
* @property {boolean} gasLimitSpecified - True if the suggesting dapp specified a gas limit, prevents auto-estimation.
* @property {string} estimatedGas - A hex string represented the estimated gas limit required to complete the transaction.
* @property {string} origin - A string representing the interface that suggested the transaction.
* @property {Object} nonceDetails - A metadata object containing information used to derive the suggested nonce, useful for debugging nonce issues.
* @property {string} rawTx - A hex string of the final signed transaction, ready to submit to the network.
* @property {string} hash - A hex string of the transaction hash, used to identify the transaction on the network.
* @property {number} submittedTime - The time the transaction was submitted to the network, in Unix epoch time (ms).
*/
/**
* The data emitted from the MetaMaskController.store EventEmitter, also used to initialize the MetaMaskController. Available in UI on React state as state.metamask.
* @typedef MetaMaskState
* @property {boolean} isInitialized - Whether the first vault has been created.
* @property {boolean} isUnlocked - Whether the vault is currently decrypted and accounts are available for selection.
* @property {boolean} isAccountMenuOpen - Represents whether the main account selection UI is currently displayed.
* @property {boolean} isMascara - True if the current context is the extensionless MetaMascara project.
* @property {boolean} isPopup - Returns true if the current view is an externally-triggered notification.
* @property {string} rpcTarget - DEPRECATED - The URL of the current RPC provider.
* @property {Object} identities - An object matching lower-case hex addresses to Identity objects with "address" and "name" (nickname) keys.
* @property {Object} unapprovedTxs - An object mapping transaction hashes to unapproved transactions.
* @property {boolean} noActiveNotices - False if there are notices the user should confirm before using the application.
* @property {Array} frequentRpcList - A list of frequently used RPCs, including custom user-provided ones.
* @property {Array} addressBook - A list of previously sent to addresses.
* @property {address} selectedTokenAddress - Used to indicate if a token is globally selected. Should be deprecated in favor of UI-centric token selection.
* @property {Object} tokenExchangeRates - Info about current token prices.
* @property {Array} tokens - Tokens held by the current user, including their balances.
* @property {Object} send - TODO: Document
* @property {Object} coinOptions - TODO: Document
* @property {boolean} useBlockie - Indicates preferred user identicon format. True for blockie, false for Jazzicon.
* @property {Object} featureFlags - An object for optional feature flags.
* @property {string} networkEndpointType - TODO: Document
* @property {boolean} isRevealingSeedWords - True if seed words are currently being recovered, and should be shown to user.
* @property {boolean} welcomeScreen - True if welcome screen should be shown.
* @property {string} currentLocale - A locale string matching the user's preferred display language.
* @property {Object} provider - The current selected network provider.
* @property {string} provider.rpcTarget - The address for the RPC API, if using an RPC API.
* @property {string} provider.type - An identifier for the type of network selected, allows MetaMask to use custom provider strategies for known networks.
* @property {string} network - A stringified number of the current network ID.
* @property {Object} accounts - An object mapping lower-case hex addresses to objects with "balance" and "address" keys, both storing hex string values.
* @property {hex} currentBlockGasLimit - The most recently seen block gas limit, in a lower case hex prefixed string.
* @property {TransactionMeta[]} selectedAddressTxList - An array of transactions associated with the currently selected account.
* @property {Object} unapprovedMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
* @property {number} unapprovedMsgCount - The number of messages in unapprovedMsgs.
* @property {Object} unapprovedPersonalMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
* @property {number} unapprovedPersonalMsgCount - The number of messages in unapprovedPersonalMsgs.
* @property {Object} unapprovedTypedMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
* @property {number} unapprovedTypedMsgCount - The number of messages in unapprovedTypedMsgs.
* @property {string[]} keyringTypes - An array of unique keyring identifying strings, representing available strategies for creating accounts.
* @property {Keyring[]} keyrings - An array of keyring descriptions, summarizing the accounts that are available for use, and what keyrings they belong to.
* @property {Object} computedBalances - Maps accounts to their balances, accounting for balance changes from pending transactions.
* @property {string} currentAccountTab - A view identifying string for displaying the current displayed view, allows user to have a preferred tab in the old UI (between tokens and history).
* @property {string} selectedAddress - A lower case hex string of the currently selected address.
* @property {string} currentCurrency - A string identifying the user's preferred display currency, for use in showing conversion rates.
* @property {number} conversionRate - A number representing the current exchange rate from the user's preferred currency to Ether.
* @property {number} conversionDate - A unix epoch date (ms) for the time the current conversion rate was last retrieved.
* @property {Object} infuraNetworkStatus - An object of infura network status checks.
* @property {Block[]} recentBlocks - An array of recent blocks, used to calculate an effective but cheap gas price.
* @property {Array} shapeShiftTxList - An array of objects describing shapeshift exchange attempts.
* @property {Array} lostAccounts - TODO: Remove this feature. A leftover from the version-3 migration where our seed-phrase library changed to fix a bug where some accounts were mis-generated, but we recovered the old accounts as "lost" instead of losing them.
* @property {boolean} forgottenPassword - Returns true if the user has initiated the password recovery screen, is recovering from seed phrase.
*/
/**
* @typedef VersionedData
* @property {MetaMaskState} data - The data emitted from MetaMask controller, or used to initialize it.
* @property {Number} version - The latest migration version that has been run.
*/
/**
* Initializes the MetaMask controller, and sets up all platform configuration.
* @returns {Promise} Setup complete.
*/
async function initialize () {
const initState = await loadStateFromPersistence()
const initLangCode = await getFirstPreferredLangCode()
@ -68,6 +160,11 @@ async function initialize () {
// State and Persistence
//
/**
* Loads any stored data, prioritizing the latest storage strategy.
* Migrates that data schema in case it was last loaded on an older version.
* @returns {Promise<MetaMaskState>} Last data emitted from previous instance of MetaMask.
*/
async function loadStateFromPersistence () {
// migrations
const migrator = new Migrator({ migrations })
@ -130,6 +227,16 @@ async function loadStateFromPersistence () {
return versionedData.data
}
/**
* Initializes the MetaMask Controller with any initial state and default language.
* Configures platform-specific error reporting strategy.
* Streams emitted state updates to platform-specific storage strategy.
* Creates platform listeners for new Dapps/Contexts, and sets up their data connections to the controller.
*
* @param {Object} initState - The initial state to start the controller with, matches the state that is emitted from the controller.
* @param {String} initLangCode - The region code for the language preferred by the current user.
* @returns {Promise} After setup is complete.
*/
function setupController (initState, initLangCode) {
//
// MetaMask Controller
@ -168,12 +275,17 @@ function setupController (initState, initLangCode) {
}
)
/**
* Assigns the given state to the versioned object (with metadata), and returns that.
* @param {Object} state - The state object as emitted by the MetaMaskController.
* @returns {VersionedData} The state object wrapped in an object that includes a metadata key.
*/
function versionifyData (state) {
versionedData.data = state
return versionedData
}
function persistData(state) {
function persistData (state) {
if (!state) {
throw new Error('MetaMask - updated state is missing', state)
}
@ -192,30 +304,65 @@ function setupController (initState, initLangCode) {
//
// connect to other contexts
//
extension.runtime.onConnect.addListener(connectRemote)
const metamaskInternalProcessHash = {
[ENVIRONMENT_TYPE_POPUP]: true,
[ENVIRONMENT_TYPE_NOTIFICATION]: true,
[ENVIRONMENT_TYPE_FULLSCREEN]: true,
}
const isClientOpenStatus = () => {
return popupIsOpen || Boolean(Object.keys(openMetamaskTabsIDs).length) || notificationIsOpen
}
/**
* A runtime.Port object, as provided by the browser:
* @link https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/Port
* @typedef Port
* @type Object
*/
/**
* Connects a Port to the MetaMask controller via a multiplexed duplex stream.
* This method identifies trusted (MetaMask) interfaces, and connects them differently from untrusted (web pages).
* @param {Port} remotePort - The port provided by a new context.
*/
function connectRemote (remotePort) {
const isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification'
const processName = remotePort.name
const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName]
const portStream = new PortStream(remotePort)
if (isMetaMaskInternalProcess) {
// communication with popup
popupIsOpen = popupIsOpen || (remotePort.name === 'popup')
controller.isClientOpen = true
controller.setupTrustedCommunication(portStream, 'MetaMask')
// record popup as closed
if (remotePort.sender.url.match(/home.html$/)) {
openMetamaskTabsIDs[remotePort.sender.tab.id] = true
}
if (remotePort.name === 'popup') {
if (processName === ENVIRONMENT_TYPE_POPUP) {
popupIsOpen = true
endOfStream(portStream, () => {
popupIsOpen = false
if (remotePort.sender.url.match(/home.html$/)) {
openMetamaskTabsIDs[remotePort.sender.tab.id] = false
}
controller.isClientOpen = isClientOpenStatus()
})
}
if (remotePort.name === 'notification') {
if (processName === ENVIRONMENT_TYPE_NOTIFICATION) {
notificationIsOpen = true
endOfStream(portStream, () => {
notificationIsOpen = false
controller.isClientOpen = isClientOpenStatus()
})
}
if (processName === ENVIRONMENT_TYPE_FULLSCREEN) {
const tabId = remotePort.sender.tab.id
openMetamaskTabsIDs[tabId] = true
endOfStream(portStream, () => {
delete openMetamaskTabsIDs[tabId]
controller.isClientOpen = isClientOpenStatus()
})
}
} else {
@ -234,7 +381,10 @@ function setupController (initState, initLangCode) {
controller.messageManager.on('updateBadge', updateBadge)
controller.personalMessageManager.on('updateBadge', updateBadge)
// plugin badge text
/**
* Updates the Web Extension's "badge" number, on the little fox in the toolbar.
* The number reflects the current number of pending transactions or message signatures needing user approval.
*/
function updateBadge () {
var label = ''
var unapprovedTxCount = controller.txController.getUnapprovedTxCount()
@ -256,12 +406,15 @@ function setupController (initState, initLangCode) {
// Etc...
//
// popup trigger
/**
* Opens the browser popup for user confirmation
*/
function triggerUi () {
extension.tabs.query({ active: true }, (tabs) => {
const currentlyActiveMetamaskTab = tabs.find(tab => openMetamaskTabsIDs[tab.id])
if (!popupIsOpen && !currentlyActiveMetamaskTab && !notificationIsOpen) notificationManager.showPopup()
notificationIsOpen = true
extension.tabs.query({ active: true }, tabs => {
const currentlyActiveMetamaskTab = Boolean(tabs.find(tab => openMetamaskTabsIDs[tab.id]))
if (!popupIsOpen && !currentlyActiveMetamaskTab && !notificationIsOpen) {
notificationManager.showPopup()
}
})
}

View File

@ -1,44 +0,0 @@
const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask'
const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask'
const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
const LOCALHOST_RPC_URL = 'http://localhost:8545'
const MAINET_RPC_URL_BETA = 'https://mainnet.infura.io/metamask2'
const ROPSTEN_RPC_URL_BETA = 'https://ropsten.infura.io/metamask2'
const KOVAN_RPC_URL_BETA = 'https://kovan.infura.io/metamask2'
const RINKEBY_RPC_URL_BETA = 'https://rinkeby.infura.io/metamask2'
const DEFAULT_RPC = 'rinkeby'
const OLD_UI_NETWORK_TYPE = 'network'
const BETA_UI_NETWORK_TYPE = 'networkBeta'
global.METAMASK_DEBUG = process.env.METAMASK_DEBUG
module.exports = {
network: {
localhost: LOCALHOST_RPC_URL,
mainnet: MAINET_RPC_URL,
ropsten: ROPSTEN_RPC_URL,
kovan: KOVAN_RPC_URL,
rinkeby: RINKEBY_RPC_URL,
},
// Used for beta UI
networkBeta: {
localhost: LOCALHOST_RPC_URL,
mainnet: MAINET_RPC_URL_BETA,
ropsten: ROPSTEN_RPC_URL_BETA,
kovan: KOVAN_RPC_URL_BETA,
rinkeby: RINKEBY_RPC_URL_BETA,
},
networkNames: {
3: 'Ropsten',
4: 'Rinkeby',
42: 'Kovan',
},
enums: {
DEFAULT_RPC,
OLD_UI_NETWORK_TYPE,
BETA_UI_NETWORK_TYPE,
},
}

View File

@ -23,6 +23,9 @@ if (shouldInjectWeb3()) {
setupStreams()
}
/**
* Creates a script tag that injects inpage.js
*/
function setupInjection () {
try {
// inject in-page script
@ -37,6 +40,10 @@ function setupInjection () {
}
}
/**
* Sets up two-way communication streams between the
* browser extension and local per-page browser context
*/
function setupStreams () {
// setup communication to page and plugin
const pageStream = new LocalMessageDuplexStream({
@ -89,17 +96,34 @@ function setupStreams () {
mux.ignoreStream('publicConfig')
}
/**
* Error handler for page to plugin stream disconnections
*
* @param {string} remoteLabel Remote stream name
* @param {Error} err Stream connection error
*/
function logStreamDisconnectWarning (remoteLabel, err) {
let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}`
if (err) warningMsg += '\n' + err.stack
console.warn(warningMsg)
}
/**
* Determines if Web3 should be injected
*
* @returns {boolean} {@code true} if Web3 should be injected
*/
function shouldInjectWeb3 () {
return doctypeCheck() && suffixCheck()
&& documentElementCheck() && !blacklistedDomainCheck()
}
/**
* Checks the doctype of the current document if it exists
*
* @returns {boolean} {@code true} if the doctype is html or if none exists
*/
function doctypeCheck () {
const doctype = window.document.doctype
if (doctype) {
@ -109,6 +133,11 @@ function doctypeCheck () {
}
}
/**
* Checks the current document extension
*
* @returns {boolean} {@code true} if the current extension is not prohibited
*/
function suffixCheck () {
var prohibitedTypes = ['xml', 'pdf']
var currentUrl = window.location.href
@ -122,6 +151,11 @@ function suffixCheck () {
return true
}
/**
* Checks the documentElement of the current document
*
* @returns {boolean} {@code true} if the documentElement is an html node or if none exists
*/
function documentElementCheck () {
var documentElement = document.documentElement.nodeName
if (documentElement) {
@ -130,6 +164,11 @@ function documentElementCheck () {
return true
}
/**
* Checks if the current domain is blacklisted
*
* @returns {boolean} {@code true} if the current domain is blacklisted
*/
function blacklistedDomainCheck () {
var blacklistedDomains = [
'uscourts.gov',
@ -148,6 +187,9 @@ function blacklistedDomainCheck () {
return false
}
/**
* Redirects the current page to a phishing information page
*/
function redirectToPhishingWarning () {
console.log('MetaMask - redirecting to phishing warning')
window.location.href = 'https://metamask.io/phishing.html'

View File

@ -4,9 +4,22 @@ const extend = require('xtend')
class AddressBookController {
// Controller in charge of managing the address book functionality from the
// recipients field on the send screen. Manages a history of all saved
// addresses and all currently owned addresses.
/**
* Controller in charge of managing the address book functionality from the
* recipients field on the send screen. Manages a history of all saved
* addresses and all currently owned addresses.
*
* @typedef {Object} AddressBookController
* @param {object} opts Overrides the defaults for the initial state of this.store
* @property {array} opts.initState initializes the the state of the AddressBookController. Can contain an
* addressBook property to initialize the addressBook array
* @param {KeyringController} keyringController (Soon to be deprecated) The keyringController used in the current
* MetamaskController. Contains the identities used in this AddressBookController.
* @property {object} store The the store of the current users address book
* @property {array} store.addressBook An array of addresses and nicknames. These are set by the user when sending
* to a new address.
*
*/
constructor (opts = {}, keyringController) {
const initState = extend({
addressBook: [],
@ -19,7 +32,14 @@ class AddressBookController {
// PUBLIC METHODS
//
// Sets a new address book in store by accepting a new address and nickname.
/**
* Sets a new address book in store by accepting a new address and nickname.
*
* @param {string} address A hex address of a new account that the user is sending to.
* @param {string} name The name the user wishes to associate with the new account
* @returns {Promise<void>} Promise resolves with undefined
*
*/
setAddressBook (address, name) {
return this._addToAddressBook(address, name)
.then((addressBook) => {
@ -30,14 +50,16 @@ class AddressBookController {
})
}
//
// PRIVATE METHODS
//
// Performs the logic to add the address and name into the address book. The
// pushed object is an object of two fields. Current behavior does not set an
// upper limit to the number of addresses.
/**
* Performs the logic to add the address and name into the address book. The pushed object is an object of two
* fields. Current behavior does not set an upper limit to the number of addresses.
*
* @private
* @param {string} address A hex address of a new account that the user is sending to.
* @param {string} name The name the user wishes to associate with the new account
* @returns {Promise<array>} Promises the updated addressBook array
*
*/
_addToAddressBook (address, name) {
const addressBook = this._getAddressBook()
const identities = this._getIdentities()
@ -62,14 +84,26 @@ class AddressBookController {
return Promise.resolve(addressBook)
}
// Internal method to get the address book. Current persistence behavior
// should not require that this method be called from the UI directly.
/**
* Internal method to get the address book. Current persistence behavior should not require that this method be
* called from the UI directly.
*
* @private
* @returns {array} The addressBook array from the store.
*
*/
_getAddressBook () {
return this.store.getState().addressBook
}
// Retrieves identities from the keyring controller in order to avoid
// duplication
/**
* Retrieves identities from the keyring controller in order to avoid
* duplication
*
* @deprecated
* @returns {array} Returns the identies array from the keyringContoller's state
*
*/
_getIdentities () {
return this.keyringController.memStore.getState().identities
}

View File

@ -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')

View File

@ -2,8 +2,24 @@ const ObservableStore = require('obs-store')
const extend = require('xtend')
const BalanceController = require('./balance')
class ComputedbalancesController {
/**
* @typedef {Object} ComputedBalancesOptions
* @property {Object} accountTracker Account tracker store reference
* @property {Object} txController Token controller reference
* @property {Object} blockTracker Block tracker reference
* @property {Object} initState Initial state to populate this internal store with
*/
/**
* Background controller responsible for syncing
* and computing ETH balances for all accounts
*/
class ComputedbalancesController {
/**
* Creates a new controller instance
*
* @param {ComputedBalancesOptions} [opts] Controller configuration parameters
*/
constructor (opts = {}) {
const { accountTracker, txController, blockTracker } = opts
this.accountTracker = accountTracker
@ -19,6 +35,9 @@ class ComputedbalancesController {
this._initBalanceUpdating()
}
/**
* Updates balances associated with each internal address
*/
updateAllBalances () {
Object.keys(this.balances).forEach((balance) => {
const address = balance.address
@ -26,12 +45,23 @@ class ComputedbalancesController {
})
}
/**
* Initializes internal address tracking
*
* @private
*/
_initBalanceUpdating () {
const store = this.accountTracker.store.getState()
this.syncAllAccountsFromStore(store)
this.accountTracker.store.subscribe(this.syncAllAccountsFromStore.bind(this))
}
/**
* Uses current account state to sync and track all
* addresses associated with the current account
*
* @param {{ accounts: Object }} store Account tracking state
*/
syncAllAccountsFromStore (store) {
const upstream = Object.keys(store.accounts)
const balances = Object.keys(this.balances)
@ -50,6 +80,13 @@ class ComputedbalancesController {
})
}
/**
* Conditionally establishes a new subscription
* to track an address associated with the current
* account
*
* @param {string} address Address to conditionally subscribe to
*/
trackAddressIfNotAlready (address) {
const state = this.store.getState()
if (!(address in state.computedBalances)) {
@ -57,6 +94,12 @@ class ComputedbalancesController {
}
}
/**
* Establishes a new subscription to track an
* address associated with the current account
*
* @param {string} address Address to conditionally subscribe to
*/
trackAddress (address) {
const updater = new BalanceController({
address,

View File

@ -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)

View File

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

View File

@ -0,0 +1,56 @@
const ROPSTEN = 'ropsten'
const RINKEBY = 'rinkeby'
const KOVAN = 'kovan'
const MAINNET = 'mainnet'
const LOCALHOST = 'localhost'
const ROPSTEN_CODE = 3
const RINKEYBY_CODE = 4
const KOVAN_CODE = 42
const ROPSTEN_DISPLAY_NAME = 'Ropsten'
const RINKEBY_DISPLAY_NAME = 'Rinkeby'
const KOVAN_DISPLAY_NAME = 'Kovan'
const MAINNET_DISPLAY_NAME = 'Main Ethereum Network'
const MAINNET_RPC_URL = 'https://mainnet.infura.io/metamask'
const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask'
const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
const LOCALHOST_RPC_URL = 'http://localhost:8545'
const MAINNET_RPC_URL_BETA = 'https://mainnet.infura.io/metamask2'
const ROPSTEN_RPC_URL_BETA = 'https://ropsten.infura.io/metamask2'
const KOVAN_RPC_URL_BETA = 'https://kovan.infura.io/metamask2'
const RINKEBY_RPC_URL_BETA = 'https://rinkeby.infura.io/metamask2'
const DEFAULT_NETWORK = 'rinkeby'
const OLD_UI_NETWORK_TYPE = 'network'
const BETA_UI_NETWORK_TYPE = 'networkBeta'
module.exports = {
ROPSTEN,
RINKEBY,
KOVAN,
MAINNET,
LOCALHOST,
ROPSTEN_CODE,
RINKEYBY_CODE,
KOVAN_CODE,
ROPSTEN_DISPLAY_NAME,
RINKEBY_DISPLAY_NAME,
KOVAN_DISPLAY_NAME,
MAINNET_DISPLAY_NAME,
MAINNET_RPC_URL,
ROPSTEN_RPC_URL,
KOVAN_RPC_URL,
RINKEBY_RPC_URL,
LOCALHOST_RPC_URL,
MAINNET_RPC_URL_BETA,
ROPSTEN_RPC_URL_BETA,
KOVAN_RPC_URL_BETA,
RINKEBY_RPC_URL_BETA,
DEFAULT_NETWORK,
OLD_UI_NETWORK_TYPE,
BETA_UI_NETWORK_TYPE,
}

View File

@ -0,0 +1,2 @@
const NetworkController = require('./network')
module.exports = NetworkController

View File

@ -7,10 +7,18 @@ const ObservableStore = require('obs-store')
const ComposedStore = require('obs-store/lib/composed')
const extend = require('xtend')
const EthQuery = require('eth-query')
const createEventEmitterProxy = require('../lib/events-proxy.js')
const networkConfig = require('../config.js')
const { OLD_UI_NETWORK_TYPE, DEFAULT_RPC } = networkConfig.enums
const INFURA_PROVIDER_TYPES = ['ropsten', 'rinkeby', 'kovan', 'mainnet']
const createEventEmitterProxy = require('../../lib/events-proxy.js')
const log = require('loglevel')
const {
ROPSTEN,
RINKEBY,
KOVAN,
MAINNET,
OLD_UI_NETWORK_TYPE,
DEFAULT_NETWORK,
} = require('./enums')
const { getNetworkEndpoints } = require('./util')
const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET]
module.exports = class NetworkController extends EventEmitter {
@ -18,8 +26,8 @@ module.exports = class NetworkController extends EventEmitter {
super()
this._networkEndpointVersion = OLD_UI_NETWORK_TYPE
this._networkEndpoints = this.getNetworkEndpoints(OLD_UI_NETWORK_TYPE)
this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
this._networkEndpoints = getNetworkEndpoints(OLD_UI_NETWORK_TYPE)
this._defaultRpc = this._networkEndpoints[DEFAULT_NETWORK]
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider)
this.networkStore = new ObservableStore('loading')
@ -36,17 +44,13 @@ module.exports = class NetworkController extends EventEmitter {
}
this._networkEndpointVersion = version
this._networkEndpoints = this.getNetworkEndpoints(version)
this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
this._networkEndpoints = getNetworkEndpoints(version)
this._defaultRpc = this._networkEndpoints[DEFAULT_NETWORK]
const { type } = this.getProviderConfig()
return this.setProviderType(type, true)
}
getNetworkEndpoints (version = OLD_UI_NETWORK_TYPE) {
return networkConfig[version]
}
initializeProvider (_providerParams) {
this._baseProviderParams = _providerParams
const { type, rpcTarget } = this.providerStore.getState()

View File

@ -0,0 +1,65 @@
const {
ROPSTEN,
RINKEBY,
KOVAN,
MAINNET,
LOCALHOST,
ROPSTEN_CODE,
RINKEYBY_CODE,
KOVAN_CODE,
ROPSTEN_DISPLAY_NAME,
RINKEBY_DISPLAY_NAME,
KOVAN_DISPLAY_NAME,
MAINNET_DISPLAY_NAME,
MAINNET_RPC_URL,
ROPSTEN_RPC_URL,
KOVAN_RPC_URL,
RINKEBY_RPC_URL,
LOCALHOST_RPC_URL,
MAINNET_RPC_URL_BETA,
ROPSTEN_RPC_URL_BETA,
KOVAN_RPC_URL_BETA,
RINKEBY_RPC_URL_BETA,
OLD_UI_NETWORK_TYPE,
BETA_UI_NETWORK_TYPE,
} = require('./enums')
const networkToNameMap = {
[ROPSTEN]: ROPSTEN_DISPLAY_NAME,
[RINKEBY]: RINKEBY_DISPLAY_NAME,
[KOVAN]: KOVAN_DISPLAY_NAME,
[MAINNET]: MAINNET_DISPLAY_NAME,
[ROPSTEN_CODE]: ROPSTEN_DISPLAY_NAME,
[RINKEYBY_CODE]: RINKEBY_DISPLAY_NAME,
[KOVAN_CODE]: KOVAN_DISPLAY_NAME,
}
const networkEndpointsMap = {
[OLD_UI_NETWORK_TYPE]: {
[LOCALHOST]: LOCALHOST_RPC_URL,
[MAINNET]: MAINNET_RPC_URL,
[ROPSTEN]: ROPSTEN_RPC_URL,
[KOVAN]: KOVAN_RPC_URL,
[RINKEBY]: RINKEBY_RPC_URL,
},
[BETA_UI_NETWORK_TYPE]: {
[LOCALHOST]: LOCALHOST_RPC_URL,
[MAINNET]: MAINNET_RPC_URL_BETA,
[ROPSTEN]: ROPSTEN_RPC_URL_BETA,
[KOVAN]: KOVAN_RPC_URL_BETA,
[RINKEBY]: RINKEBY_RPC_URL_BETA,
},
}
const getNetworkDisplayName = key => networkToNameMap[key]
const getNetworkEndpoints = (networkType = OLD_UI_NETWORK_TYPE) => {
return {
...networkEndpointsMap[networkType],
}
}
module.exports = {
getNetworkDisplayName,
getNetworkEndpoints,
}

View File

@ -4,6 +4,21 @@ const extend = require('xtend')
class PreferencesController {
/**
*
* @typedef {Object} PreferencesController
* @param {object} opts Overrides the defaults for the initial state of this.store
* @property {object} store The an object containing a users preferences, stored in local storage
* @property {array} store.frequentRpcList A list of custom rpcs to provide the user
* @property {string} store.currentAccountTab Indicates the selected tab in the ui
* @property {array} store.tokens The tokens the user wants display in their token lists
* @property {boolean} store.useBlockie The users preference for blockie identicons within the UI
* @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the
* user wishes to see that feature
* @property {string} store.currentLocale The preferred language locale key
* @property {string} store.selectedAddress A hex string that matches the currently selected address in the app
*
*/
constructor (opts = {}) {
const initState = extend({
frequentRpcList: [],
@ -17,18 +32,43 @@ class PreferencesController {
}
// PUBLIC METHODS
/**
* Setter for the `useBlockie` property
*
* @param {boolean} val Whether or not the user prefers blockie indicators
*
*/
setUseBlockie (val) {
this.store.updateState({ useBlockie: val })
}
/**
* Getter for the `useBlockie` property
*
* @returns {boolean} this.store.useBlockie
*
*/
getUseBlockie () {
return this.store.getState().useBlockie
}
/**
* Setter for the `currentLocale` property
*
* @param {string} key he preferred language locale key
*
*/
setCurrentLocale (key) {
this.store.updateState({ currentLocale: key })
}
/**
* Setter for the `selectedAddress` property
*
* @param {string} _address A new hex address for an account
* @returns {Promise<void>} Promise resolves with undefined
*
*/
setSelectedAddress (_address) {
return new Promise((resolve, reject) => {
const address = normalizeAddress(_address)
@ -37,10 +77,37 @@ class PreferencesController {
})
}
/**
* Getter for the `selectedAddress` property
*
* @returns {string} The hex address for the currently selected account
*
*/
getSelectedAddress () {
return this.store.getState().selectedAddress
}
/**
* Contains data about tokens users add to their account.
* @typedef {Object} AddedToken
* @property {string} address - The hex address for the token contract. Will be all lower cased and hex-prefixed.
* @property {string} symbol - The symbol of the token, usually 3 or 4 capitalized letters
* {@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#symbol}
* @property {boolean} decimals - The number of decimals the token uses.
* {@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#decimals}
*/
/**
* Adds a new token to the token array, or updates the token if passed an address that already exists.
* Modifies the existing tokens array from the store. All objects in the tokens array array AddedToken objects.
* @see AddedToken {@link AddedToken}
*
* @param {string} rawAddress Hex address of the token contract. May or may not be a checksum address.
* @param {string} symbol The symbol of the token
* @param {number} decimals The number of decimals the token uses.
* @returns {Promise<array>} Promises the new array of AddedToken objects.
*
*/
async addToken (rawAddress, symbol, decimals) {
const address = normalizeAddress(rawAddress)
const newEntry = { address, symbol, decimals }
@ -62,6 +129,13 @@ class PreferencesController {
return Promise.resolve(tokens)
}
/**
* Removes a specified token from the tokens array.
*
* @param {string} rawAddress Hex address of the token contract to remove.
* @returns {Promise<array>} The new array of AddedToken objects
*
*/
removeToken (rawAddress) {
const tokens = this.store.getState().tokens
@ -71,10 +145,23 @@ class PreferencesController {
return Promise.resolve(updatedTokens)
}
/**
* A getter for the `tokens` property
*
* @returns {array} The current array of AddedToken objects
*
*/
getTokens () {
return this.store.getState().tokens
}
/**
* Gets an updated rpc list from this.addToFrequentRpcList() and sets the `frequentRpcList` to this update list.
*
* @param {string} _url The the new rpc url to add to the updated list
* @returns {Promise<void>} Promise resolves with undefined
*
*/
updateFrequentRpcList (_url) {
return this.addToFrequentRpcList(_url)
.then((rpcList) => {
@ -83,6 +170,13 @@ class PreferencesController {
})
}
/**
* Setter for the `currentAccountTab` property
*
* @param {string} currentAccountTab Specifies the new tab to be marked as current
* @returns {Promise<void>} Promise resolves with undefined
*
*/
setCurrentAccountTab (currentAccountTab) {
return new Promise((resolve, reject) => {
this.store.updateState({ currentAccountTab })
@ -90,6 +184,15 @@ class PreferencesController {
})
}
/**
* Returns an updated rpcList based on the passed url and the current list.
* The returned list will have a max length of 2. If the _url currently exists it the list, it will be moved to the
* end of the list. The current list is modified and returned as a promise.
*
* @param {string} _url The rpc url to add to the frequentRpcList.
* @returns {Promise<array>} The updated frequentRpcList.
*
*/
addToFrequentRpcList (_url) {
const rpcList = this.getFrequentRpcList()
const index = rpcList.findIndex((element) => { return element === _url })
@ -105,10 +208,24 @@ class PreferencesController {
return Promise.resolve(rpcList)
}
/**
* Getter for the `frequentRpcList` property.
*
* @returns {array<string>} An array of one or two rpc urls.
*
*/
getFrequentRpcList () {
return this.store.getState().frequentRpcList
}
/**
* Updates the `featureFlags` property, which is an object. One property within that object will be set to a boolean.
*
* @param {string} feature A key that corresponds to a UI feature.
* @param {boolean} activated Indicates whether or not the UI feature should be displayed
* @returns {Promise<object>} Promises a new object; the updated featureFlags object.
*
*/
setFeatureFlag (feature, activated) {
const currentFeatureFlags = this.store.getState().featureFlags
const updatedFeatureFlags = {
@ -121,6 +238,13 @@ class PreferencesController {
return Promise.resolve(updatedFeatureFlags)
}
/**
* A getter for the `featureFlags` property
*
* @returns {object} A key-boolean map, where keys refer to features and booleans to whether the
* user wishes to see that feature
*
*/
getFeatureFlags () {
return this.store.getState().featureFlags
}

View File

@ -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 {

View File

@ -1,11 +1,23 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
const log = require('loglevel')
// every three seconds when an incomplete tx is waiting
const POLLING_INTERVAL = 3000
class ShapeshiftController {
/**
* Controller responsible for managing the list of shapeshift transactions. On construction, it initiates a poll
* that queries a shapeshift.io API for updates to any pending shapeshift transactions
*
* @typedef {Object} ShapeshiftController
* @param {object} opts Overrides the defaults for the initial state of this.store
* @property {array} opts.initState initializes the the state of the ShapeshiftController. Can contain an
* shapeShiftTxList array.
* @property {array} shapeShiftTxList An array of ShapeShiftTx objects
*
*/
constructor (opts = {}) {
const initState = extend({
shapeShiftTxList: [],
@ -14,21 +26,54 @@ class ShapeshiftController {
this.pollForUpdates()
}
/**
* Represents, and contains data about, a single shapeshift transaction.
* @typedef {Object} ShapeShiftTx
* @property {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the
* user's Metamask account
* @property {string} depositType - An abbreviation of the type of crypto currency to be deposited.
* @property {string} key - The 'shapeshift' key differentiates this from other types of txs in Metamask
* @property {number} time - The time at which the tx was created
* @property {object} response - Initiated as an empty object, which will be replaced by a Response object. @see {@link
* https://developer.mozilla.org/en-US/docs/Web/API/Response}
*/
//
// PUBLIC METHODS
//
/**
* A getter for the shapeShiftTxList property
*
* @returns {array<ShapeShiftTx>}
*
*/
getShapeShiftTxList () {
const shapeShiftTxList = this.store.getState().shapeShiftTxList
return shapeShiftTxList
}
/**
* A getter for all ShapeShiftTx in the shapeShiftTxList that have not successfully completed a deposit.
*
* @returns {array<ShapeShiftTx>} Only includes ShapeShiftTx which has a response property with a status !== complete
*
*/
getPendingTxs () {
const txs = this.getShapeShiftTxList()
const pending = txs.filter(tx => tx.response && tx.response.status !== 'complete')
return pending
}
/**
* A poll that exists as long as there are pending transactions. Each call attempts to update the data of any
* pendingTxs, and then calls itself again. If there are no pending txs, the recursive call is not made and
* the polling stops.
*
* this.updateTx is used to attempt the update to the pendingTxs in the ShapeShiftTxList, and that updated data
* is saved with saveTx.
*
*/
pollForUpdates () {
const pendingTxs = this.getPendingTxs()
@ -45,6 +90,15 @@ class ShapeshiftController {
})
}
/**
* Attempts to update a ShapeShiftTx with data from a shapeshift.io API. Both the response and time properties
* can be updated. The response property is updated with every call, but the time property is only updated when
* the response status updates to 'complete'. This will occur once the user makes a deposit as the ShapeShiftTx
* depositAddress
*
* @param {ShapeShiftTx} tx The tx to update
*
*/
async updateTx (tx) {
try {
const url = `https://shapeshift.io/txStat/${tx.depositAddress}`
@ -60,6 +114,13 @@ class ShapeshiftController {
}
}
/**
* Saves an updated to a ShapeShiftTx in the shapeShiftTxList. If the passed ShapeShiftTx is not in the
* shapeShiftTxList, nothing happens.
*
* @param {ShapeShiftTx} tx The updated tx to save, if it exists in the current shapeShiftTxList
*
*/
saveTx (tx) {
const { shapeShiftTxList } = this.store.getState()
const index = shapeShiftTxList.indexOf(tx)
@ -69,6 +130,12 @@ class ShapeshiftController {
}
}
/**
* Removes a ShapeShiftTx from the shapeShiftTxList
*
* @param {ShapeShiftTx} tx The tx to remove
*
*/
removeShapeShiftTx (tx) {
const { shapeShiftTxList } = this.store.getState()
const index = shapeShiftTxList.indexOf(index)
@ -78,6 +145,14 @@ class ShapeshiftController {
this.updateState({ shapeShiftTxList })
}
/**
* Creates a new ShapeShiftTx, adds it to the shapeShiftTxList, and initiates a new poll for updates of pending txs
*
* @param {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the
* user's Metamask account
* @param {string} depositType - An abbreviation of the type of crypto currency to be deposited.
*
*/
createShapeShiftTx (depositAddress, depositType) {
const state = this.store.getState()
let { shapeShiftTxList } = state

View File

@ -0,0 +1,77 @@
const ObservableStore = require('obs-store')
// By default, poll every 3 minutes
const DEFAULT_INTERVAL = 180 * 1000
/**
* A controller that polls for token exchange
* rates based on a user's current token list
*/
class TokenRatesController {
/**
* Creates a TokenRatesController
*
* @param {Object} [config] - Options to configure controller
*/
constructor ({ interval = DEFAULT_INTERVAL, preferences } = {}) {
this.store = new ObservableStore()
this.preferences = preferences
this.interval = interval
}
/**
* Updates exchange rates for all tokens
*/
async updateExchangeRates () {
if (!this.isActive) { return }
const contractExchangeRates = {}
for (const i in this._tokens) {
const address = this._tokens[i].address
contractExchangeRates[address] = await this.fetchExchangeRate(address)
}
this.store.putState({ contractExchangeRates })
}
/**
* Fetches a token exchange rate by address
*
* @param {String} address - Token contract address
*/
async fetchExchangeRate (address) {
try {
const response = await fetch(`https://metamask.dev.balanc3.net/prices?from=${address}&to=ETH&autoConversion=false&summaryOnly=true`)
const json = await response.json()
return json && json.length ? json[0].averagePrice : 0
} catch (error) { }
}
/**
* @type {Number}
*/
set interval (interval) {
this._handle && clearInterval(this._handle)
if (!interval) { return }
this._handle = setInterval(() => { this.updateExchangeRates() }, interval)
}
/**
* @type {Object}
*/
set preferences (preferences) {
this._preferences && this._preferences.unsubscribe()
if (!preferences) { return }
this._preferences = preferences
this.tokens = preferences.getState().tokens
preferences.subscribe(({ tokens = [] }) => { this.tokens = tokens })
}
/**
* @type {Array}
*/
set tokens (tokens) {
this._tokens = tokens
this.updateExchangeRates()
}
}
module.exports = TokenRatesController

View File

@ -7,6 +7,7 @@ const TransactionStateManager = require('../lib/tx-state-manager')
const TxGasUtil = require('../lib/tx-gas-utils')
const PendingTransactionTracker = require('../lib/pending-tx-tracker')
const NonceTracker = require('../lib/nonce-tracker')
const log = require('loglevel')
/*
Transaction Controller is an aggregate of sub-controllers and trackers

View File

@ -1,69 +1,97 @@
const asmcrypto = require('asmcrypto.js')
const Unibabel = require('browserify-unibabel')
/**
* A Microsoft Edge-specific encryption class that exposes
* the interface expected by eth-keykeyring-controller
*/
class EdgeEncryptor {
/**
* Encrypts an arbitrary object to ciphertext
*
* @param {string} password Used to generate a key to encrypt the data
* @param {Object} dataObject Data to encrypt
* @returns {Promise<string>} Promise resolving to an object with ciphertext
*/
encrypt (password, dataObject) {
var salt = this._generateSalt()
return this._keyFromPassword(password, salt)
.then(function (key) {
var data = JSON.stringify(dataObject)
var dataBuffer = Unibabel.utf8ToBuffer(data)
var vector = global.crypto.getRandomValues(new Uint8Array(16))
var resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector)
encrypt (password, dataObject) {
var buffer = new Uint8Array(resultbuffer)
var vectorStr = Unibabel.bufferToBase64(vector)
var vaultStr = Unibabel.bufferToBase64(buffer)
return JSON.stringify({
data: vaultStr,
iv: vectorStr,
salt: salt,
})
})
}
var salt = this._generateSalt()
return this._keyFromPassword(password, salt)
.then(function (key) {
/**
* Decrypts an arbitrary object from ciphertext
*
* @param {string} password Used to generate a key to decrypt the data
* @param {string} text Ciphertext of an encrypted object
* @returns {Promise<Object>} Promise resolving to copy of decrypted object
*/
decrypt (password, text) {
const payload = JSON.parse(text)
const salt = payload.salt
return this._keyFromPassword(password, salt)
.then(function (key) {
const encryptedData = Unibabel.base64ToBuffer(payload.data)
const vector = Unibabel.base64ToBuffer(payload.iv)
return new Promise((resolve, reject) => {
var result
try {
result = asmcrypto.AES_GCM.decrypt(encryptedData, key, vector)
} catch (err) {
return reject(new Error('Incorrect password'))
}
const decryptedData = new Uint8Array(result)
const decryptedStr = Unibabel.bufferToUtf8(decryptedData)
const decryptedObj = JSON.parse(decryptedStr)
resolve(decryptedObj)
})
})
}
var data = JSON.stringify(dataObject)
var dataBuffer = Unibabel.utf8ToBuffer(data)
var vector = global.crypto.getRandomValues(new Uint8Array(16))
var resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector)
/**
* Retrieves a cryptographic key using a password
*
* @private
* @param {string} password Password used to unlock a cryptographic key
* @param {string} salt Random base64 data
* @returns {Promise<Object>} Promise resolving to a derived key
*/
_keyFromPassword (password, salt) {
var buffer = new Uint8Array(resultbuffer)
var vectorStr = Unibabel.bufferToBase64(vector)
var vaultStr = Unibabel.bufferToBase64(buffer)
return JSON.stringify({
data: vaultStr,
iv: vectorStr,
salt: salt,
})
})
}
var passBuffer = Unibabel.utf8ToBuffer(password)
var saltBuffer = Unibabel.base64ToBuffer(salt)
return new Promise((resolve) => {
var key = asmcrypto.PBKDF2_HMAC_SHA256.bytes(passBuffer, saltBuffer, 10000)
resolve(key)
})
}
decrypt (password, text) {
const payload = JSON.parse(text)
const salt = payload.salt
return this._keyFromPassword(password, salt)
.then(function (key) {
const encryptedData = Unibabel.base64ToBuffer(payload.data)
const vector = Unibabel.base64ToBuffer(payload.iv)
return new Promise((resolve, reject) => {
var result
try {
result = asmcrypto.AES_GCM.decrypt(encryptedData, key, vector)
} catch (err) {
return reject(new Error('Incorrect password'))
}
const decryptedData = new Uint8Array(result)
const decryptedStr = Unibabel.bufferToUtf8(decryptedData)
const decryptedObj = JSON.parse(decryptedStr)
resolve(decryptedObj)
})
})
}
_keyFromPassword (password, salt) {
var passBuffer = Unibabel.utf8ToBuffer(password)
var saltBuffer = Unibabel.base64ToBuffer(salt)
return new Promise((resolve) => {
var key = asmcrypto.PBKDF2_HMAC_SHA256.bytes(passBuffer, saltBuffer, 10000)
resolve(key)
})
}
_generateSalt (byteCount = 32) {
var view = new Uint8Array(byteCount)
global.crypto.getRandomValues(view)
var b64encoded = btoa(String.fromCharCode.apply(null, view))
return b64encoded
}
/**
* Generates random base64 encoded data
*
* @private
* @returns {string} Randomized base64 encoded data
*/
_generateSalt (byteCount = 32) {
var view = new Uint8Array(byteCount)
global.crypto.getRandomValues(view)
var b64encoded = btoa(String.fromCharCode.apply(null, view))
return b64encoded
}
}
module.exports = EdgeEncryptor

View File

@ -1,15 +1,24 @@
// test and development environment variables
const env = process.env.METAMASK_ENV
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
const { DEFAULT_NETWORK, MAINNET } = require('./controllers/network/enums')
//
// The default state of MetaMask
//
module.exports = {
/**
* @typedef {Object} FirstTimeState
* @property {Object} config Initial configuration parameters
* @property {Object} NetworkController Network controller state
*/
/**
* @type {FirstTimeState}
*/
const initialState = {
config: {},
NetworkController: {
provider: {
type: (METAMASK_DEBUG || env === 'test') ? 'rinkeby' : 'mainnet',
type: (METAMASK_DEBUG || env === 'test') ? DEFAULT_NETWORK : MAINNET,
},
},
}
module.exports = initialState

View File

@ -3,16 +3,11 @@ cleanContextForImports()
require('web3/dist/web3.min.js')
const log = require('loglevel')
const LocalMessageDuplexStream = require('post-message-stream')
// const PingStream = require('ping-pong-stream/ping')
// const endOfStream = require('end-of-stream')
const setupDappAutoReload = require('./lib/auto-reload.js')
const MetamaskInpageProvider = require('./lib/inpage-provider.js')
restoreContextAfterImports()
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
window.log = log
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
//
// setup plugin communication
@ -47,20 +42,20 @@ log.debug('MetaMask - injected web3')
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
// set web3 defaultAccount
inpageProvider.publicConfigStore.subscribe(function (state) {
web3.eth.defaultAccount = state.selectedAddress
})
//
// util
//
// need to make sure we aren't affected by overlapping namespaces
// and that we dont affect the app with our namespace
// mostly a fix for web3's BigNumber if AMD's "define" is defined...
var __define
/**
* Caches reference to global define object and deletes it to
* avoid conflicts with other global define objects, such as
* AMD's define function
*/
function cleanContextForImports () {
__define = global.define
try {
@ -70,6 +65,9 @@ function cleanContextForImports () {
}
}
/**
* Restores global define object from cached reference
*/
function restoreContextAfterImports () {
try {
global.define = __define

View File

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

View File

@ -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) {

View File

@ -1,12 +1,11 @@
const ethUtil = require('ethereumjs-util')
const normalize = require('eth-sig-util').normalize
const MetamaskConfig = require('../config.js')
const MAINNET_RPC = MetamaskConfig.network.mainnet
const ROPSTEN_RPC = MetamaskConfig.network.ropsten
const KOVAN_RPC = MetamaskConfig.network.kovan
const RINKEBY_RPC = MetamaskConfig.network.rinkeby
const {
MAINNET_RPC_URL,
ROPSTEN_RPC_URL,
KOVAN_RPC_URL,
RINKEBY_RPC_URL,
} = require('../controllers/network/enums')
/* The config-manager is a convenience object
* wrapping a pojo-migrator.
@ -102,7 +101,6 @@ ConfigManager.prototype.setShowSeedWords = function (should) {
this.setData(data)
}
ConfigManager.prototype.getShouldShowSeedWords = function () {
var data = this.getData()
return data.showSeedWords
@ -118,6 +116,27 @@ ConfigManager.prototype.getSeedWords = function () {
var data = this.getData()
return data.seedWords
}
/**
* Called to set the isRevealingSeedWords flag. This happens only when the user chooses to reveal
* the seed words and not during the first time flow.
* @param {boolean} reveal - Value to set the isRevealingSeedWords flag.
*/
ConfigManager.prototype.setIsRevealingSeedWords = function (reveal = false) {
const data = this.getData()
data.isRevealingSeedWords = reveal
this.setData(data)
}
/**
* Returns the isRevealingSeedWords flag.
* @returns {boolean|undefined}
*/
ConfigManager.prototype.getIsRevealingSeedWords = function () {
const data = this.getData()
return data.isRevealingSeedWords
}
ConfigManager.prototype.setRpcTarget = function (rpcUrl) {
var config = this.getConfig()
config.provider = {
@ -154,19 +173,19 @@ ConfigManager.prototype.getCurrentRpcAddress = function () {
switch (provider.type) {
case 'mainnet':
return MAINNET_RPC
return MAINNET_RPC_URL
case 'ropsten':
return ROPSTEN_RPC
return ROPSTEN_RPC_URL
case 'kovan':
return KOVAN_RPC
return KOVAN_RPC_URL
case 'rinkeby':
return RINKEBY_RPC
return RINKEBY_RPC_URL
default:
return provider && provider.rpcTarget ? provider.rpcTarget : RINKEBY_RPC
return provider && provider.rpcTarget ? provider.rpcTarget : RINKEBY_RPC_URL
}
}

View File

@ -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()
})
}

View File

@ -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()
}
}

View File

@ -1,6 +1,10 @@
module.exports = createProviderMiddleware
// forward requests to provider
/**
* Forwards an HTTP request to the current Web3 provider
*
* @param {{ provider: Object }} config Configuration containing current Web3 provider
*/
function createProviderMiddleware ({ provider }) {
return (req, res, next, end) => {
provider.sendAsync(req, (err, _res) => {

9
app/scripts/lib/enums.js Normal file
View File

@ -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,
}

View File

@ -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'
}
}

View File

@ -1,26 +1,37 @@
/**
* Returns an EventEmitter that proxies events from the given event emitter
* @param {any} eventEmitter
* @param {object} listeners - The listeners to proxy to
* @returns {any}
*/
module.exports = function createEventEmitterProxy (eventEmitter, listeners) {
let target = eventEmitter
const eventHandlers = listeners || {}
const proxy = new Proxy({}, {
get: (obj, name) => {
const proxy = /** @type {any} */ (new Proxy({}, {
get: (_, name) => {
// intercept listeners
if (name === 'on') return addListener
if (name === 'setTarget') return setTarget
if (name === 'proxyEventHandlers') return eventHandlers
return target[name]
return (/** @type {any} */ (target))[name]
},
set: (obj, name, value) => {
set: (_, name, value) => {
target[name] = value
return true
},
})
function setTarget (eventEmitter) {
}))
function setTarget (/** @type {EventEmitter} */ eventEmitter) {
target = eventEmitter
// migrate listeners
Object.keys(eventHandlers).forEach((name) => {
eventHandlers[name].forEach((handler) => target.on(name, handler))
/** @type {Array<Function>} */ (eventHandlers[name]).forEach((handler) => target.on(name, handler))
})
}
/**
* Attaches a function to be called whenever the specified event is emitted
* @param {string} name
* @param {Function} handler
*/
function addListener (name, handler) {
if (!eventHandlers[name]) eventHandlers[name] = []
eventHandlers[name].push(handler)

View File

@ -4,6 +4,13 @@ const allLocales = require('../../_locales/index.json')
const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().replace('_', '-'))
/**
* Returns a preferred language code, based on settings within the user's browser. If we have no translations for the
* users preferred locales, 'en' is returned.
*
* @returns {Promise<string>} Promises a locale code, either one from the user's preferred list that we have a translation for, or 'en'
*
*/
async function getFirstPreferredLangCode () {
const userPreferredLocaleCodes = await promisify(
extension.i18n.getAcceptLanguages,

View File

@ -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)
}

View File

@ -1,12 +0,0 @@
module.exports = function isPopupOrNotification () {
const url = window.location.href
// if (url.match(/popup.html$/) || url.match(/home.html$/)) {
// Below regexes needed for feature toggles (e.g. see line ~340 in ui/app/app.js)
// Revert below regexes to above commented out regexes before merge to master
if (url.match(/popup.html(?:\?.+)*$/) ||
url.match(/home.html(?:\?.+)*$/) || url.match(/home.html(?:#.*)*$/)) {
return 'popup'
} else {
return 'notification'
}
}

View File

@ -1,10 +1,13 @@
// We should not rely on local storage in an extension!
// We should use this instead!
// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/storage/local
const extension = require('extensionizer')
const log = require('loglevel')
/**
* A wrapper around the extension's storage local API
*/
module.exports = class ExtensionStore {
/**
* @constructor
*/
constructor() {
this.isSupported = !!(extension.storage.local)
if (!this.isSupported) {
@ -12,6 +15,10 @@ module.exports = class ExtensionStore {
}
}
/**
* Returns all of the keys currently saved
* @return {Promise<*>}
*/
async get() {
if (!this.isSupported) return undefined
const result = await this._get()
@ -24,14 +31,24 @@ module.exports = class ExtensionStore {
}
}
/**
* Sets the key in local state
* @param {object} state - The state to set
* @return {Promise<void>}
*/
async set(state) {
return this._set(state)
}
/**
* Returns all of the keys currently saved
* @private
* @return {object} the key-value map from local storage
*/
_get() {
const local = extension.storage.local
return new Promise((resolve, reject) => {
local.get(null, (result) => {
local.get(null, (/** @type {any} */ result) => {
const err = extension.runtime.lastError
if (err) {
reject(err)
@ -42,6 +59,12 @@ module.exports = class ExtensionStore {
})
}
/**
* Sets the key in local state
* @param {object} obj - The key to set
* @return {Promise<void>}
* @private
*/
_set(obj) {
const local = extension.storage.local
return new Promise((resolve, reject) => {
@ -57,6 +80,11 @@ module.exports = class ExtensionStore {
}
}
/**
* Returns whether or not the given object contains no keys
* @param {object} obj - The object to check
* @returns {boolean}
*/
function isEmpty(obj) {
return Object.keys(obj).length === 0
}

View File

@ -1,7 +1,23 @@
const EventEmitter = require('events')
/**
* @typedef {object} Migration
* @property {number} version - The migration version
* @property {Function} migrate - Returns a promise of the migrated data
*/
/**
* @typedef {object} MigratorOptions
* @property {Array<Migration>} [migrations] - The list of migrations to apply
* @property {number} [defaultVersion] - The version to use in the initial state
*/
class Migrator extends EventEmitter {
/**
* @constructor
* @param {MigratorOptions} opts
*/
constructor (opts = {}) {
super()
const migrations = opts.migrations || []
@ -42,19 +58,30 @@ class Migrator extends EventEmitter {
return versionedData
// migration is "pending" if it has a higher
// version number than currentVersion
/**
* Returns whether or not the migration is pending
*
* A migration is considered "pending" if it has a higher
* version number than the current version.
* @param {Migration} migration
* @returns {boolean}
*/
function migrationIsPending (migration) {
return migration.version > versionedData.meta.version
}
}
generateInitialState (initState) {
/**
* Returns the initial state for the migrator
* @param {object} [data] - The data for the initial state
* @returns {{meta: {version: number}, data: any}}
*/
generateInitialState (data) {
return {
meta: {
version: this.defaultVersion,
},
data: initState,
data,
}
}

View File

@ -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)

View File

@ -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 {

View File

@ -6,6 +6,13 @@ module.exports = PortDuplexStream
inherits(PortDuplexStream, Duplex)
/**
* Creates a stream that's both readable and writable.
* The stream supports arbitrary objects.
*
* @class
* @param {Object} port Remote Port object
*/
function PortDuplexStream (port) {
Duplex.call(this, {
objectMode: true,
@ -15,8 +22,13 @@ function PortDuplexStream (port) {
port.onDisconnect.addListener(this._onDisconnect.bind(this))
}
// private
/**
* Callback triggered when a message is received from
* the remote Port associated with this Stream.
*
* @private
* @param {Object} msg - Payload from the onMessage listener of Port
*/
PortDuplexStream.prototype._onMessage = function (msg) {
if (Buffer.isBuffer(msg)) {
delete msg._isBuffer
@ -27,14 +39,31 @@ PortDuplexStream.prototype._onMessage = function (msg) {
}
}
/**
* Callback triggered when the remote Port
* associated with this Stream disconnects.
*
* @private
*/
PortDuplexStream.prototype._onDisconnect = function () {
this.destroy()
}
// stream plumbing
/**
* Explicitly sets read operations to a no-op
*/
PortDuplexStream.prototype._read = noop
/**
* Called internally when data should be written to
* this writable stream.
*
* @private
* @param {*} msg Arbitrary object to write
* @param {string} encoding Encoding to use when writing payload
* @param {Function} cb Called when writing is complete or an error occurs
*/
PortDuplexStream.prototype._write = function (msg, encoding, cb) {
try {
if (Buffer.isBuffer(msg)) {

View File

@ -1,4 +1,5 @@
const KeyringController = require('eth-keyring-controller')
const log = require('loglevel')
const seedPhraseVerifier = {

View File

@ -1,6 +1,9 @@
module.exports = setupMetamaskMeshMetrics
/**
* Injects an iframe into the current document for testing
*/
function setupMetamaskMeshMetrics() {
const testingContainer = document.createElement('iframe')
testingContainer.src = 'https://metamask.github.io/mesh-testing/'

View File

@ -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(

View File

@ -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) {

View File

@ -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,
}

View File

@ -5,10 +5,10 @@
*/
const EventEmitter = require('events')
const extend = require('xtend')
const pump = require('pump')
const Dnode = require('dnode')
const ObservableStore = require('obs-store')
const ComposableObservableStore = require('./lib/ComposableObservableStore')
const asStream = require('obs-store/lib/asStream')
const AccountTracker = require('./lib/account-tracker')
const RpcEngine = require('json-rpc-engine')
@ -34,6 +34,7 @@ const PersonalMessageManager = require('./lib/personal-message-manager')
const TypedMessageManager = require('./lib/typed-message-manager')
const TransactionController = require('./controllers/transactions')
const BalancesController = require('./controllers/computed-balances')
const TokenRatesController = require('./controllers/token-rates')
const ConfigManager = require('./lib/config-manager')
const nodeify = require('./lib/nodeify')
const accountImporter = require('./account-import-strategies')
@ -44,6 +45,7 @@ const BN = require('ethereumjs-util').BN
const GWEI_BN = new BN('1000000000')
const percentile = require('percentile')
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
const log = require('loglevel')
module.exports = class MetamaskController extends EventEmitter {
@ -65,7 +67,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.platform = opts.platform
// observable state store
this.store = new ObservableStore(initState)
this.store = new ComposableObservableStore(initState)
// lock to ensure only one vault created at once
this.createVaultMutex = new Mutex()
@ -104,6 +106,11 @@ module.exports = class MetamaskController extends EventEmitter {
this.provider = this.initializeProvider()
this.blockTracker = this.provider._blockTracker
// token exchange rate tracker
this.tokenRatesController = new TokenRatesController({
preferences: this.preferencesController.store,
})
this.recentBlocksController = new RecentBlocksController({
blockTracker: this.blockTracker,
provider: this.provider,
@ -184,53 +191,37 @@ module.exports = class MetamaskController extends EventEmitter {
this.typedMessageManager = new TypedMessageManager()
this.publicConfigStore = this.initPublicConfigStore()
// manual disk state subscriptions
this.txController.store.subscribe((state) => {
this.store.updateState({ TransactionController: state })
})
this.keyringController.store.subscribe((state) => {
this.store.updateState({ KeyringController: state })
})
this.preferencesController.store.subscribe((state) => {
this.store.updateState({ PreferencesController: state })
})
this.addressBookController.store.subscribe((state) => {
this.store.updateState({ AddressBookController: state })
})
this.currencyController.store.subscribe((state) => {
this.store.updateState({ CurrencyController: state })
})
this.noticeController.store.subscribe((state) => {
this.store.updateState({ NoticeController: state })
})
this.shapeshiftController.store.subscribe((state) => {
this.store.updateState({ ShapeShiftController: state })
})
this.networkController.store.subscribe((state) => {
this.store.updateState({ NetworkController: state })
this.store.updateStructure({
TransactionController: this.txController.store,
KeyringController: this.keyringController.store,
PreferencesController: this.preferencesController.store,
AddressBookController: this.addressBookController.store,
CurrencyController: this.currencyController.store,
NoticeController: this.noticeController.store,
ShapeShiftController: this.shapeshiftController.store,
NetworkController: this.networkController.store,
InfuraController: this.infuraController.store,
})
this.infuraController.store.subscribe((state) => {
this.store.updateState({ InfuraController: state })
this.memStore = new ComposableObservableStore(null, {
NetworkController: this.networkController.store,
AccountTracker: this.accountTracker.store,
TxController: this.txController.memStore,
BalancesController: this.balancesController.store,
TokenRatesController: this.tokenRatesController.store,
MessageManager: this.messageManager.memStore,
PersonalMessageManager: this.personalMessageManager.memStore,
TypesMessageManager: this.typedMessageManager.memStore,
KeyringController: this.keyringController.memStore,
PreferencesController: this.preferencesController.store,
RecentBlocksController: this.recentBlocksController.store,
AddressBookController: this.addressBookController.store,
CurrencyController: this.currencyController.store,
NoticeController: this.noticeController.memStore,
ShapeshiftController: this.shapeshiftController.store,
InfuraController: this.infuraController.store,
})
// manual mem state subscriptions
const sendUpdate = this.sendUpdate.bind(this)
this.networkController.store.subscribe(sendUpdate)
this.accountTracker.store.subscribe(sendUpdate)
this.txController.memStore.subscribe(sendUpdate)
this.balancesController.store.subscribe(sendUpdate)
this.messageManager.memStore.subscribe(sendUpdate)
this.personalMessageManager.memStore.subscribe(sendUpdate)
this.typedMessageManager.memStore.subscribe(sendUpdate)
this.keyringController.memStore.subscribe(sendUpdate)
this.preferencesController.store.subscribe(sendUpdate)
this.recentBlocksController.store.subscribe(sendUpdate)
this.addressBookController.store.subscribe(sendUpdate)
this.currencyController.store.subscribe(sendUpdate)
this.noticeController.memStore.subscribe(sendUpdate)
this.shapeshiftController.store.subscribe(sendUpdate)
this.infuraController.store.subscribe(sendUpdate)
this.memStore.subscribe(this.sendUpdate.bind(this))
}
/**
@ -272,6 +263,7 @@ module.exports = class MetamaskController extends EventEmitter {
/**
* Constructor helper: initialize a public config store.
* This store is used to make some config info available to Dapps synchronously.
*/
initPublicConfigStore () {
// get init state
@ -279,6 +271,7 @@ module.exports = class MetamaskController extends EventEmitter {
// memStore -> transform -> publicConfigStore
this.on('update', (memState) => {
this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen
const publicState = selectPublicState(memState)
publicConfigStore.putState(publicState)
})
@ -308,39 +301,25 @@ module.exports = class MetamaskController extends EventEmitter {
const vault = this.keyringController.store.getState().vault
const isInitialized = (!!wallet || !!vault)
return extend(
{
isInitialized,
},
this.networkController.store.getState(),
this.accountTracker.store.getState(),
this.txController.memStore.getState(),
this.messageManager.memStore.getState(),
this.personalMessageManager.memStore.getState(),
this.typedMessageManager.memStore.getState(),
this.keyringController.memStore.getState(),
this.balancesController.store.getState(),
this.preferencesController.store.getState(),
this.addressBookController.store.getState(),
this.currencyController.store.getState(),
this.noticeController.memStore.getState(),
this.infuraController.store.getState(),
this.recentBlocksController.store.getState(),
// config manager
this.configManager.getConfig(),
this.shapeshiftController.store.getState(),
{
return {
...{ isInitialized },
...this.memStore.getFlatState(),
...this.configManager.getConfig(),
...{
lostAccounts: this.configManager.getLostAccounts(),
seedWords: this.configManager.getSeedWords(),
forgottenPassword: this.configManager.getPasswordForgotten(),
}
)
isRevealingSeedWords: Boolean(this.configManager.getIsRevealingSeedWords()),
},
}
}
/**
* Returns an api-object which is consumed by the UI
* Returns an Object containing API Callback Functions.
* These functions are the interface for the UI.
* The API object can be transmitted over a stream with dnode.
*
* @returns {Object}
* @returns {Object} Object containing API functions.
*/
getApi () {
const keyringController = this.keyringController
@ -372,6 +351,7 @@ module.exports = class MetamaskController extends EventEmitter {
clearSeedWordCache: this.clearSeedWordCache.bind(this),
resetAccount: nodeify(this.resetAccount, this),
importAccountWithStrategy: this.importAccountWithStrategy.bind(this),
setIsRevealingSeedWords: this.configManager.setIsRevealingSeedWords.bind(this.configManager),
// vault management
submitPassword: nodeify(keyringController.submitPassword, keyringController),
@ -430,16 +410,18 @@ module.exports = class MetamaskController extends EventEmitter {
//=============================================================================
/**
* Creates a new Vault(?) and create a new keychain(?)
* Creates a new Vault and create a new keychain.
*
* A vault is ...
* A vault, or KeyringController, is a controller that contains
* many different account strategies, currently called Keyrings.
* Creating it new means wiping all previous keyrings.
*
* A keychain is ...
* A keychain, or keyring, controls many accounts with a single backup and signing strategy.
* For example, a mnemonic phrase can generate many accounts, and is a keyring.
*
* @param {string} password
*
* @param {} password
*
* @returns {} vault
* @returns {Object} vault
*/
async createNewVaultAndKeychain (password) {
const release = await this.createVaultMutex.acquire()
@ -465,7 +447,7 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
* Create a new Vault and restore an existent keychain
* Create a new Vault and restore an existent keyring.
* @param {} password
* @param {} seed
*/
@ -482,11 +464,17 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
/**
* @type Identity
* @property {string} name - The account nickname.
* @property {string} address - The account's ethereum address, in lower case.
* @property {boolean} mayBeFauceting - Whether this account is currently
* receiving funds from our automatic Ropsten faucet.
*/
/**
* Retrieves the first Identiy from the passed Vault and selects the related address
*
* An Identity is ...
*
* @param {} vault
*/
selectFirstIdentity (vault) {
@ -495,12 +483,12 @@ module.exports = class MetamaskController extends EventEmitter {
this.preferencesController.setSelectedAddress(address)
}
// ?
// Opinionated Keyring Management
//
// Account Management
//
/**
* Adds a new account to ...
* Adds a new account to the default (first) HD seed phrase Keyring.
*
* @returns {} keyState
*/
@ -530,6 +518,8 @@ module.exports = class MetamaskController extends EventEmitter {
*
* Used when creating a first vault, to allow confirmation.
* Also used when revealing the seed words in the confirmation view.
*
* @param {Function} cb - A callback called on completion.
*/
placeSeedWords (cb) {
@ -549,6 +539,8 @@ module.exports = class MetamaskController extends EventEmitter {
* Validity: seed phrase restores the accounts belonging to the current vault.
*
* Called when the first account is created and on unlocking the vault.
*
* @returns {Promise<string>} Seed phrase to be confirmed by the user.
*/
async verifySeedPhrase () {
@ -579,6 +571,7 @@ module.exports = class MetamaskController extends EventEmitter {
*
* The seed phrase remains available in the background process.
*
* @param {function} cb Callback function called with the current address.
*/
clearSeedWordCache (cb) {
this.configManager.setSeedWords(null)
@ -586,9 +579,13 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
* ?
* Clears the transaction history, to allow users to force-reset their nonces.
* Mostly used in development environments, when networks are restarted with
* the same network ID.
*
* @returns Promise<string> The current selected address.
*/
async resetAccount (cb) {
async resetAccount () {
const selectedAddress = this.preferencesController.getSelectedAddress()
this.txController.wipeTransactions(selectedAddress)
@ -600,11 +597,13 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
* Imports an account ... ?
* Imports an account with the specified import strategy.
* These are defined in app/scripts/account-import-strategies
* Each strategy represents a different way of serializing an Ethereum key pair.
*
* @param {} strategy
* @param {} args
* @param {} cb
* @param {string} strategy - A unique identifier for an account import strategy.
* @param {any} args - The data required by that strategy to import an account.
* @param {Function} cb - A callback function called with a state update on success.
*/
importAccountWithStrategy (strategy, args, cb) {
accountImporter.importAccount(strategy, args)
@ -618,13 +617,42 @@ module.exports = class MetamaskController extends EventEmitter {
}
// ---------------------------------------------------------------------------
// Identity Management (sign)
// Identity Management (signature operations)
// eth_sign methods:
/**
* @param {} msgParams
* @param {} cb
* Called when a Dapp uses the eth_sign method, to request user approval.
* eth_sign is a pure signature of arbitrary data. It is on a deprecation
* path, since this data can be a transaction, or can leak private key
* information.
*
* @param {Object} msgParams - The params passed to eth_sign.
* @param {Function} cb = The callback function called with the signature.
*/
signMessage (msgParams, cb) {
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)}`))
}
})
}
/**
* Signifies user intent to complete an eth_sign method.
*
* @param {Object} msgParams The params passed to eth_call.
* @returns {Promise<Object>} Full state update.
*/
signMessage (msgParams) {
log.info('MetaMaskController - signMessage')
const msgId = msgParams.metamaskId
@ -643,148 +671,32 @@ module.exports = class MetamaskController extends EventEmitter {
})
}
// Prefixed Style Message Signing Methods:
/**
* Used to cancel a message submitted via eth_sign.
*
* @param {} msgParams
* @param {} cb
* @param {string} msgId - The id of the message to cancel.
*/
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)
cancelMessage (msgId, cb) {
const messageManager = this.messageManager
messageManager.rejectMsg(msgId)
if (cb && typeof cb === 'function') {
cb(null, this.getState())
}
return Promise.resolve(migratorOutput)
}
// personal_sign methods:
/**
* Import (lost) Accounts
* Called when a dapp uses the personal_sign method.
* This is identical to the Geth eth_sign method, and may eventually replace
* eth_sign.
*
* @param {Object} {lostAccounts} @Array accounts <{ address, privateKey }>
* We currently define our eth_sign and personal_sign mostly for legacy Dapps.
*
* Uses the array's private keys to create a new Simple Key Pair keychain
* and add it to the keyring controller.
* @param {Object} msgParams - The params of the message to sign & return to the Dapp.
* @param {Function} cb - The callback function called with the signature.
* Passed back to the requesting Dapp.
*/
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.'))
@ -805,6 +717,52 @@ module.exports = class MetamaskController extends EventEmitter {
})
}
/**
* Signifies a user's approval to sign a personal_sign message in queue.
* Triggers signing, and the callback function from newUnsignedPersonalMessage.
*
* @param {Object} msgParams - The params of the message to sign & return to the Dapp.
* @returns {Promise<Object>} - A full state update.
*/
signPersonalMessage (msgParams) {
log.info('MetaMaskController - signPersonalMessage')
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()
})
}
/**
* Used to cancel a personal_sign type message.
* @param {string} msgId - The ID of the message to cancel.
* @param {Function} cb - The callback function called with a full state update.
*/
cancelPersonalMessage (msgId, cb) {
const messageManager = this.personalMessageManager
messageManager.rejectMsg(msgId)
if (cb && typeof cb === 'function') {
cb(null, this.getState())
}
}
// eth_signTypedData methods
/**
* Called when a dapp uses the eth_signTypedData method, per EIP 712.
*
* @param {Object} msgParams - The params passed to eth_signTypedData.
* @param {Function} cb - The callback function, called with the signature.
*/
newUnsignedTypedMessage (msgParams, cb) {
let msgId
try {
@ -827,22 +785,36 @@ module.exports = class MetamaskController extends EventEmitter {
})
}
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())
}
/**
* The method for a user approving a call to eth_signTypedData, per EIP 712.
* Triggers the callback in newUnsignedTypedMessage.
*
* @param {Object} msgParams - The params passed to eth_signTypedData.
* @returns {Object} Full state update.
*/
signTypedMessage (msgParams) {
log.info('MetaMaskController - signTypedMessage')
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()
})
}
/**
* Used to cancel a eth_signTypedData type message.
* @param {string} msgId - The ID of the message to cancel.
* @param {Function} cb - The callback function called with a full state update.
*/
cancelTypedMessage (msgId, cb) {
const messageManager = this.typedMessageManager
messageManager.rejectMsg(msgId)
@ -851,18 +823,119 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
// ---------------------------------------------------------------------------
// MetaMask Version 3 Migration Account Restauration Methods
/**
* A legacy method (probably dead code) that was used when we swapped out our
* key management library that we depended on.
*
* Described in:
* https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd
*
* @deprecated
* @param {} migratorOutput
*/
restoreOldVaultAccounts (migratorOutput) {
const { serialized } = migratorOutput
return this.keyringController.restoreKeyring(serialized)
.then(() => migratorOutput)
}
/**
* A legacy method used to record user confirmation that they understand
* that some of their accounts have been recovered but should be backed up.
*
* @deprecated
* @param {Function} cb - A callback function called with a full state update.
*/
markAccountsFound (cb) {
this.configManager.setLostAccounts([])
this.sendUpdate()
cb(null, this.getState())
}
/**
* A legacy method (probably dead code) that was used when we swapped out our
* key management library that we depended on.
*
* Described in:
* https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd
*
* @deprecated
* @param {} migratorOutput
*/
restoreOldLostAccounts (migratorOutput) {
const { lostAccounts } = migratorOutput
if (lostAccounts) {
this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address))
return this.importLostAccounts(migratorOutput)
}
return Promise.resolve(migratorOutput)
}
/**
* An account object
* @typedef Account
* @property string privateKey - The private key of the account.
*/
/**
* Probably no longer needed, related to the Version 3 migration.
* Imports a hash of accounts to private keys into the vault.
*
* Described in:
* https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd
*
* Uses the array's private keys to create a new Simple Key Pair keychain
* and add it to the keyring controller.
* @deprecated
* @param {Account[]} lostAccounts -
* @returns {Keyring[]} An array of the restored keyrings.
*/
importLostAccounts ({ lostAccounts }) {
const privKeys = lostAccounts.map(acct => acct.privateKey)
return this.keyringController.restoreKeyring({
type: 'Simple Key Pair',
data: privKeys,
})
}
//=============================================================================
// END (VAULT / KEYRING RELATED METHODS)
//=============================================================================
/**
* Allows a user to try to speed up a transaction by retrying it
* with higher gas.
*
* @param {string} txId - The ID of the transaction to speed up.
* @param {Function} cb - The callback function called with a full state update.
*/
async retryTransaction (txId, cb) {
await this.txController.retryTransaction(txId)
const state = await this.getState()
return state
}
//=============================================================================
// PASSWORD MANAGEMENT
//=============================================================================
/**
* Allows a user to begin the seed phrase recovery process.
* @param {Function} cb - A callback function called when complete.
*/
markPasswordForgotten(cb) {
this.configManager.setPasswordForgotten(true)
this.sendUpdate()
cb()
}
/**
* Allows a user to end the seed phrase recovery process.
* @param {Function} cb - A callback function called when complete.
*/
unMarkPasswordForgotten(cb) {
this.configManager.setPasswordForgotten(false)
this.sendUpdate()
@ -873,6 +946,13 @@ module.exports = class MetamaskController extends EventEmitter {
// SETUP
//=============================================================================
/**
* Used to create a multiplexed stream for connecting to an untrusted context
* like a Dapp or other extension.
* @param {*} connectionStream - The Duplex stream to connect to.
* @param {string} originDomain - The domain requesting the stream, which
* may trigger a blacklist reload.
*/
setupUntrustedCommunication (connectionStream, originDomain) {
// Check if new connection is blacklisted
if (this.blacklistController.checkForPhishing(originDomain)) {
@ -888,6 +968,16 @@ module.exports = class MetamaskController extends EventEmitter {
this.setupPublicConfig(mux.createStream('publicConfig'))
}
/**
* Used to create a multiplexed stream for connecting to a trusted context,
* like our own user interfaces, which have the provider APIs, but also
* receive the exported API from this controller, which includes trusted
* functions, like the ability to approve transactions or sign messages.
*
* @param {*} connectionStream - The duplex stream to connect to.
* @param {string} originDomain - The domain requesting the connection,
* used in logging and error reporting.
*/
setupTrustedCommunication (connectionStream, originDomain) {
// setup multiplexing
const mux = setupMultiplex(connectionStream)
@ -896,12 +986,25 @@ module.exports = class MetamaskController extends EventEmitter {
this.setupProviderConnection(mux.createStream('provider'), originDomain)
}
/**
* Called when we detect a suspicious domain. Requests the browser redirects
* to our anti-phishing page.
*
* @private
* @param {*} connectionStream - The duplex stream to the per-page script,
* for sending the reload attempt to.
* @param {string} hostname - The URL that triggered the suspicion.
*/
sendPhishingWarning (connectionStream, hostname) {
const mux = setupMultiplex(connectionStream)
const phishingStream = mux.createStream('phishing')
phishingStream.write({ hostname })
}
/**
* A method for providing our API over a stream using Dnode.
* @param {*} outStream - The stream to provide our API over.
*/
setupControllerConnection (outStream) {
const api = this.getApi()
const dnode = Dnode(api)
@ -920,6 +1023,11 @@ module.exports = class MetamaskController extends EventEmitter {
})
}
/**
* A method for serving our ethereum provider over a given stream.
* @param {*} outStream - The stream to provide over.
* @param {string} origin - The URI of the requesting resource.
*/
setupProviderConnection (outStream, origin) {
// setup json rpc engine stack
const engine = new RpcEngine()
@ -949,6 +1057,16 @@ module.exports = class MetamaskController extends EventEmitter {
)
}
/**
* A method for providing our public config info over a stream.
* This includes info we like to be synchronous if possible, like
* the current selected account, and network ID.
*
* Since synchronous methods have been deprecated in web3,
* this is a good candidate for deprecation.
*
* @param {*} outStream - The stream to provide public config over.
*/
setupPublicConfig (outStream) {
pump(
asStream(this.publicConfigStore),
@ -959,10 +1077,21 @@ module.exports = class MetamaskController extends EventEmitter {
)
}
/**
* A method for emitting the full MetaMask state to all registered listeners.
* @private
*/
privateSendUpdate () {
this.emit('update', this.getState())
}
/**
* A method for estimating a good gas price at recent prices.
* Returns the lowest price that would have been included in
* 50% of recent blocks.
*
* @returns {string} A hex representation of the suggested wei gas price.
*/
getGasPrice () {
const { recentBlocksController } = this
const { recentBlocks } = recentBlocksController.store.getState()
@ -996,6 +1125,11 @@ module.exports = class MetamaskController extends EventEmitter {
// Log blocks
/**
* A method for setting the user's preferred display currency.
* @param {string} currencyCode - The code of the preferred currency.
* @param {Function} cb - A callback function returning currency info.
*/
setCurrentCurrency (currencyCode, cb) {
try {
this.currencyController.setCurrentCurrency(currencyCode)
@ -1011,6 +1145,13 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
/**
* A method for forwarding the user to the easiest way to obtain ether,
* or the network "gas" currency, for the current selected network.
*
* @param {string} address - The address to fund.
* @param {string} amount - The amount of ether desired, as a base 10 string.
*/
buyEth (address, amount) {
if (!amount) amount = '5'
const network = this.networkController.getNetworkState()
@ -1018,18 +1159,33 @@ module.exports = class MetamaskController extends EventEmitter {
if (url) this.platform.openWindow({ url })
}
/**
* A method for triggering a shapeshift currency transfer.
* @param {string} depositAddress - The address to deposit to.
* @property {string} depositType - An abbreviation of the type of crypto currency to be deposited.
*/
createShapeShiftTx (depositAddress, depositType) {
this.shapeshiftController.createShapeShiftTx(depositAddress, depositType)
}
// network
async setCustomRpc (rpcTarget, rpcList) {
/**
* A method for selecting a custom URL for an ethereum RPC provider.
* @param {string} rpcTarget - A URL for a valid Ethereum RPC API.
* @returns {Promise<String>} - The RPC Target URL confirmed.
*/
async setCustomRpc (rpcTarget) {
this.networkController.setRpcTarget(rpcTarget)
await this.preferencesController.updateFrequentRpcList(rpcTarget)
return rpcTarget
}
/**
* Sets whether or not to use the blockie identicon format.
* @param {boolean} val - True for bockie, false for jazzicon.
* @param {Function} cb - A callback function called when complete.
*/
setUseBlockie (val, cb) {
try {
this.preferencesController.setUseBlockie(val)
@ -1039,6 +1195,11 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
/**
* A method for setting a user's current locale, affecting the language rendered.
* @param {string} key - Locale identifier.
* @param {Function} cb - A callback function called when complete.
*/
setCurrentLocale (key, cb) {
try {
this.preferencesController.setCurrentLocale(key)
@ -1048,6 +1209,11 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
/**
* A method for initializing storage the first time.
* @param {Object} initState - The default state to initialize with.
* @private
*/
recordFirstTimeInfo (initState) {
if (!('firstTimeInfo' in initState)) {
initState.firstTimeInfo = {
@ -1057,4 +1223,22 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
/**
* A method for recording whether the MetaMask user interface is open or not.
* @private
* @param {boolean} open
*/
set isClientOpen (open) {
this._isClientOpen = open
this.isClientOpenAndUnlocked = this.getState().isUnlocked && open
}
/**
* A method for activating the retrieval of price data, which should only be fetched when the UI is visible.
* @private
* @param {boolean} active - True if price data should be getting fetched.
*/
set isClientOpenAndUnlocked (active) {
this.tokenRatesController.isActive = active
}
}

View File

@ -1,20 +1,25 @@
class SwPlatform {
//
// Public
//
/**
* Reloads the platform
*/
reload () {
// you cant actually do this
global.location.reload()
// TODO: you can't actually do this
/** @type {any} */ (global).location.reload()
}
openWindow ({ url }) {
// this doesnt actually work
global.open(url, '_blank')
/**
* Opens a window
* @param {{url: string}} opts - The window options
*/
openWindow (opts) {
// TODO: this doesn't actually work
/** @type {any} */ (global).open(opts.url, '_blank')
}
/**
* Returns the platform version
* @returns {string}
*/
getVersion () {
return '<unable to read version>'
}

View File

@ -1,18 +1,23 @@
class WindowPlatform {
//
// Public
//
/**
* Reload the platform
*/
reload () {
global.location.reload()
/** @type {any} */ (global).location.reload()
}
openWindow ({ url }) {
global.open(url, '_blank')
/**
* Opens a window
* @param {{url: string}} opts - The window options
*/
openWindow (opts) {
/** @type {any} */ (global).open(opts.url, '_blank')
}
/**
* Returns the platform version
* @returns {string}
*/
getVersion () {
return '<unable to read version>'
}

View File

@ -7,10 +7,14 @@ const launchMetamaskUi = require('../../ui')
const StreamProvider = require('web3-stream-provider')
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
module.exports = initializePopup
/**
* Asynchronously initializes the MetaMask popup UI
*
* @param {{ container: Element, connectionStream: * }} config Popup configuration object
* @param {Function} cb Called when initialization is complete
*/
function initializePopup ({ container, connectionStream }, cb) {
// setup app
async.waterfall([
@ -19,6 +23,12 @@ function initializePopup ({ container, connectionStream }, cb) {
], cb)
}
/**
* Establishes streamed connections to background scripts and a Web3 provider
*
* @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection
* @param {Function} cb Called when controller connection is established
*/
function connectToAccountManager (connectionStream, cb) {
// setup communication with background
// setup multiplexing
@ -28,6 +38,11 @@ function connectToAccountManager (connectionStream, cb) {
setupWeb3Connection(mx.createStream('provider'))
}
/**
* Establishes a streamed connection to a Web3 provider
*
* @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection
*/
function setupWeb3Connection (connectionStream) {
var providerStream = new StreamProvider()
providerStream.pipe(connectionStream).pipe(providerStream)
@ -38,6 +53,12 @@ function setupWeb3Connection (connectionStream) {
global.eth = new Eth(providerStream)
}
/**
* Establishes a streamed connection to the background account manager
*
* @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection
* @param {Function} cb Called when the remote account manager connection is established
*/
function setupControllerConnection (connectionStream, cb) {
// this is a really sneaky way of adding EventEmitter api
// to a bi-directional dnode instance

View File

@ -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()
}

View File

@ -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": {}
}

View File

@ -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
}
}

View File

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

View File

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

78
docs/team.md Normal file
View File

@ -0,0 +1,78 @@
# The Team
Here is an overview of the current MetaMask team, and their primary roles and responsibilities, in the order they joined the team.
## Core Team Members
The core team maintains aspects of the main product (the extension) and the core libraries (the MetaMask Controller, provider-engine, etc).
### Aaron Davis
@kumavis
Founder / Technical Lead
Especially in charge of connection to the blockchain. Wrote [provider-engine](https://github.com/MetaMask/provider-engine), and is currently working with @hermanjunge on our JavaScript light-client.
### Dan Finlay
@danfinlay
Software Engineer / Product Lead
Focused on the deliverable, user-valuable aspects of MetaMask, including usability and documentation. Coordinates efforts between different branches of the team, and integrations with other projects.
### Frankie Pangilinan
@frankiebee
Software Engineer / Transaction Manager Lead
Frankie contributes code throughout MetaMask, but has become especially specialized in the way MetaMask manages transactions. She is also the original lead of the [Mascara](https://github.com/MetaMask/mascara) project.
### Kevin Serrano
@Zanibas
Software Engineer / Project Management Lead
Kevin is a software engineer, but also spends a lot of his time keeping the team's administrative operations running smoothly.
### Thomas Huang
@tmashuang
QA Engineer
Thomas is the head of MetaMask Quality Assurance. He both takes the final pass of branches of code before we ship to production, and is also in charge of continuously improving our automated quality assurance process.
### Christian Jeria
@cjeria
User Experience Designer
Christian is the lead of MetaMask's user experience. He is continuously designing prototypes, testing them with users, and refining them with our developers for production.
### Paul Bouchon
@bitpshr
Software Engineer
The newest member of the team! Paul is currently being onboarded, and finding his niche within the team.
## Laboratory Team Members
These team members are working on projects that will benefit MetaMask, but are not directly working on the product itself.
### Herman Junge
@hermanjunge
Software Engineer
Herman is currently leading the Mustekala project, a JavaScript, IPFS-based Ethereum light client.
## Kyokan Team Members
[Kyokan](http://kyokan.io/) is a consulting firm that has been working closely with the MetaMask team on the latest version of our user interface. Their team members are not members of ConsenSys LLC, but they contribute a lot to the project.
- Daniel Tsui (@sdsui)
- Chi Kei Chan (@chikeichan)
- Dan Miller (@danjm)
- David Yoo (@yookd)
- Whymarrh Whitby (@whymarrh)

View File

@ -9,7 +9,7 @@ import Identicon from '../../../../ui/app/components/identicon'
import { confirmSeedWords, showModal } from '../../../../ui/app/actions'
import Breadcrumbs from './breadcrumbs'
import LoadingScreen from './loading-screen'
import { DEFAULT_ROUTE } from '../../../../ui/app/routes'
import { DEFAULT_ROUTE, INITIALIZE_BACKUP_PHRASE_ROUTE } from '../../../../ui/app/routes'
class ConfirmSeedScreen extends Component {
static propTypes = {
@ -53,7 +53,7 @@ class ConfirmSeedScreen extends Component {
}
render () {
const { seedWords } = this.props
const { seedWords, history } = this.props
const { selectedSeeds, shuffledSeeds } = this.state
const isValid = seedWords === selectedSeeds.map(([_, seed]) => seed).join(' ')
@ -66,6 +66,16 @@ class ConfirmSeedScreen extends Component {
<div className="first-view-main-wrapper">
<div className="first-view-main">
<div className="backup-phrase">
<a
className="backup-phrase__back-button"
onClick={e => {
e.preventDefault()
history.push(INITIALIZE_BACKUP_PHRASE_ROUTE)
}}
href="#"
>
{`< Back`}
</a>
<Identicon address={this.props.address} diameter={70} />
<div className="backup-phrase__content-wrapper">
<div>

View File

@ -8,6 +8,7 @@ import Identicon from '../../../../ui/app/components/identicon'
import Breadcrumbs from './breadcrumbs'
import LoadingScreen from './loading-screen'
import { DEFAULT_ROUTE, INITIALIZE_CONFIRM_SEED_ROUTE } from '../../../../ui/app/routes'
import { confirmSeedWords } from '../../../../ui/app/actions'
const LockIcon = props => (
<svg
@ -44,6 +45,8 @@ class BackupPhraseScreen extends Component {
address: PropTypes.string.isRequired,
seedWords: PropTypes.string,
history: PropTypes.object,
isRevealingSeedWords: PropTypes.bool,
clearSeedWords: PropTypes.func,
};
static defaultProps = {
@ -58,6 +61,14 @@ class BackupPhraseScreen extends Component {
}
componentWillMount () {
this.checkSeedWords()
}
componentDidUpdate () {
this.checkSeedWords()
}
checkSeedWords () {
const { seedWords, history } = this.props
if (!seedWords) {
@ -92,9 +103,29 @@ class BackupPhraseScreen extends Component {
)
}
renderSecretScreen () {
renderSubmitButton () {
const { isRevealingSeedWords, clearSeedWords, history } = this.props
const { isShowingSecret } = this.state
const { history } = this.props
return isRevealingSeedWords
? <button
className="first-time-flow__button"
onClick={() => clearSeedWords().then(() => history.push(DEFAULT_ROUTE))}
disabled={!isShowingSecret}
>
Done
</button>
: <button
className="first-time-flow__button"
onClick={() => isShowingSecret && history.push(INITIALIZE_CONFIRM_SEED_ROUTE)}
disabled={!isShowingSecret}
>
Next
</button>
}
renderSecretScreen () {
const { isRevealingSeedWords } = this.props
return (
<div className="backup-phrase__content-wrapper">
@ -121,14 +152,8 @@ class BackupPhraseScreen extends Component {
</div>
</div>
<div className="backup-phrase__next-button">
<button
className="first-time-flow__button"
onClick={() => isShowingSecret && history.push(INITIALIZE_CONFIRM_SEED_ROUTE)}
disabled={!isShowingSecret}
>
Next
</button>
<Breadcrumbs total={3} currentIndex={1} />
{ this.renderSubmitButton() }
{ !isRevealingSeedWords && <Breadcrumbs total={3} currentIndex={1} />}
</div>
</div>
)
@ -150,13 +175,25 @@ class BackupPhraseScreen extends Component {
}
}
const mapStateToProps = ({ metamask, appState }) => {
const { selectedAddress, seedWords, isRevealingSeedWords } = metamask
const { isLoading } = appState
return {
seedWords,
isRevealingSeedWords,
isLoading,
address: selectedAddress,
}
}
const mapDispatchToProps = dispatch => {
return {
clearSeedWords: () => dispatch(confirmSeedWords()),
}
}
export default compose(
withRouter,
connect(
({ metamask: { selectedAddress, seedWords }, appState: { isLoading } }) => ({
seedWords,
isLoading,
address: selectedAddress,
})
)
connect(mapStateToProps, mapDispatchToProps),
)(BackupPhraseScreen)

View File

@ -3,6 +3,7 @@ const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('../../ui/app/actions')
const log = require('loglevel')
// mascara
const MascaraFirstTime = require('../../mascara/src/app/first-time').default
const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default
@ -34,7 +35,7 @@ const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
const { BETA_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums
const { BETA_UI_NETWORK_TYPE } = require('../../app/scripts/controllers/network/enums')
module.exports = connect(mapStateToProps)(App)

View File

@ -8,7 +8,7 @@ const ShapeshiftForm = require('./shapeshift-form')
const Loading = require('./loading')
const AccountPanel = require('./account-panel')
const RadioList = require('./custom-radio-list')
const networkNames = require('../../../app/scripts/config.js').networkNames
const { getNetworkDisplayName } = require('../../../app/scripts/controllers/network/util')
module.exports = connect(mapStateToProps)(BuyButtonSubview)
@ -142,7 +142,7 @@ BuyButtonSubview.prototype.primarySubview = function () {
case '3':
case '4':
case '42':
const networkName = networkNames[network]
const networkName = getNetworkDisplayName(network)
const label = `${networkName} Test Faucet`
return (
h('div.flex-column', {

View File

@ -8,6 +8,7 @@ const ENS = require('ethjs-ens')
const networkMap = require('ethjs-ens/lib/network-map.json')
const ensRE = /.+\..+$/
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
const log = require('loglevel')
module.exports = EnsInput

View File

@ -3,6 +3,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const actions = require('../../../ui/app/actions')
const clone = require('clone')
const log = require('loglevel')
const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN

View File

@ -3,6 +3,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const TokenTracker = require('eth-token-tracker')
const TokenCell = require('./token-cell.js')
const log = require('loglevel')
module.exports = TokenList

View File

@ -6,7 +6,9 @@ const actions = require('../../ui/app/actions')
const NetworkIndicator = require('./components/network')
const LoadingIndicator = require('./components/loading')
const txHelper = require('../lib/tx-helper')
const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notification')
const log = require('loglevel')
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../app/scripts/lib/enums')
const { getEnvironmentType } = require('../../app/scripts/lib/util')
const PendingTx = require('./components/pending-tx')
const PendingMsg = require('./components/pending-msg')
@ -50,7 +52,7 @@ ConfirmTxScreen.prototype.render = function () {
var txData = unconfTxList[props.index] || {}
var txParams = txData.params || {}
var isNotification = isPopupOrNotification() === 'notification'
var isNotification = getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION
log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`)
if (unconfTxList.length === 0) return h(Loading, { isLoading: true })

View File

@ -1,4 +1,5 @@
const valuesFor = require('../app/util').valuesFor
const log = require('loglevel')
module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, typedMessages, network) {
log.debug('tx-helper called with params:')

720
package-lock.json generated
View File

@ -344,6 +344,11 @@
"negotiator": "0.6.1"
}
},
"accounting": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/accounting/-/accounting-0.4.1.tgz",
"integrity": "sha1-h91BA+/39EYPHhhvXGd+1s9WaIM="
},
"acorn": {
"version": "4.0.13",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
@ -2972,6 +2977,15 @@
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
"catharsis": {
"version": "0.8.9",
"resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.9.tgz",
"integrity": "sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is=",
"dev": true,
"requires": {
"underscore-contrib": "0.3.0"
}
},
"center-align": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
@ -4073,6 +4087,16 @@
"cssom": "0.3.2"
}
},
"currency-formatter": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/currency-formatter/-/currency-formatter-1.4.2.tgz",
"integrity": "sha512-rQ5HB3DenCZwfVPdpVTuVcAORodVO0VoqIbjhdUSuy0sE2b9jBdCaVKbA355NUc2KhPbu5ojHs3WypuEwPLfNg==",
"requires": {
"accounting": "0.4.1",
"locale-currency": "0.0.1",
"object-assign": "4.1.1"
}
},
"currently-unhandled": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
@ -5727,9 +5751,9 @@
}
},
"eth-keyring-controller": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-2.1.4.tgz",
"integrity": "sha1-BRjZ2Jrw2K82Kigh5NVQqL4UqAc=",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-2.2.0.tgz",
"integrity": "sha512-f/g1ZrxciWJs2aHgpfvYmZ3ImP48GA+pobTU0EFNF/y5Yylf1zQyDw671W5opGpIt5TgV4F9sYXcvyjlgbL0Pg==",
"requires": {
"bip39": "2.4.0",
"bluebird": "3.5.1",
@ -5737,7 +5761,7 @@
"eth-hd-keyring": "1.2.2",
"eth-sig-util": "1.4.2",
"eth-simple-keyring": "1.2.1",
"ethereumjs-util": "5.1.3",
"ethereumjs-util": "5.1.5",
"loglevel": "1.6.0",
"obs-store": "2.4.1",
"promise-filter": "1.1.0"
@ -5752,35 +5776,10 @@
"object-assign": "4.1.1"
}
},
"eth-hd-keyring": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/eth-hd-keyring/-/eth-hd-keyring-1.2.2.tgz",
"integrity": "sha1-rV9HkHRDapO0ObC5XHkJXCh5GII=",
"requires": {
"bip39": "2.4.0",
"eth-sig-util": "1.4.2",
"ethereumjs-util": "5.1.3",
"ethereumjs-wallet": "0.6.0",
"events": "1.1.1",
"xtend": "4.0.1"
}
},
"eth-simple-keyring": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/eth-simple-keyring/-/eth-simple-keyring-1.2.1.tgz",
"integrity": "sha1-bXs1LcWppQINYfafryHvsvY2P0U=",
"requires": {
"eth-sig-util": "1.4.2",
"ethereumjs-util": "5.1.3",
"ethereumjs-wallet": "0.6.0",
"events": "1.1.1",
"xtend": "4.0.1"
}
},
"ethereumjs-util": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.3.tgz",
"integrity": "sha512-U/wmHagElZVxnpo3bFsvk5beFADegUcEzqtA/NfQbitAPOs6JoYq8M4SY9NfH4HD8236i63UOkkXafd7bqBL9A==",
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.5.tgz",
"integrity": "sha512-xPaSEATYJpMTCGowIt0oMZwFP4R1bxd6QsWgkcDvFL0JtXsr39p32WEcD14RscCjfP41YXZPCVWA4yAg0nrJmw==",
"requires": {
"bn.js": "4.11.8",
"create-hash": "1.1.3",
@ -5827,29 +5826,15 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": {
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#71f123b676f2b2d81bc20f343670d90045a3d3d8",
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#4ea2fdfed09e8f99117d9362d17c6b01b64a2bcf",
"ethereumjs-util": "5.1.3"
},
"dependencies": {
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#71f123b676f2b2d81bc20f343670d90045a3d3d8",
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#4ea2fdfed09e8f99117d9362d17c6b01b64a2bcf",
"requires": {
"bn.js": "4.11.8",
"ethereumjs-util": "4.5.0"
},
"dependencies": {
"ethereumjs-util": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-4.5.0.tgz",
"integrity": "sha1-PpQosxfuvaPXJg2FT93alUsfG8Y=",
"requires": {
"bn.js": "4.11.8",
"create-hash": "1.1.3",
"keccakjs": "0.2.1",
"rlp": "2.0.0",
"secp256k1": "3.4.0"
}
}
"ethereumjs-util": "5.1.3"
}
},
"ethereumjs-util": {
@ -5868,6 +5853,34 @@
}
}
},
"eth-simple-keyring": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/eth-simple-keyring/-/eth-simple-keyring-1.2.1.tgz",
"integrity": "sha1-bXs1LcWppQINYfafryHvsvY2P0U=",
"requires": {
"eth-sig-util": "1.4.2",
"ethereumjs-util": "5.1.5",
"ethereumjs-wallet": "0.6.0",
"events": "1.1.1",
"xtend": "4.0.1"
},
"dependencies": {
"ethereumjs-util": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.5.tgz",
"integrity": "sha512-xPaSEATYJpMTCGowIt0oMZwFP4R1bxd6QsWgkcDvFL0JtXsr39p32WEcD14RscCjfP41YXZPCVWA4yAg0nrJmw==",
"requires": {
"bn.js": "4.11.8",
"create-hash": "1.1.3",
"ethjs-util": "0.1.4",
"keccak": "1.4.0",
"rlp": "2.0.0",
"safe-buffer": "5.1.1",
"secp256k1": "3.4.0"
}
}
}
},
"eth-token-tracker": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/eth-token-tracker/-/eth-token-tracker-1.1.4.tgz",
@ -5915,6 +5928,61 @@
}
}
},
"ethjs": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/ethjs/-/ethjs-0.2.9.tgz",
"integrity": "sha1-yagNR7ydVg9Z53gEnSIlXlgfMSs=",
"requires": {
"bn.js": "4.11.6",
"ethjs-abi": "0.2.0",
"ethjs-contract": "0.1.9",
"ethjs-filter": "0.1.5",
"ethjs-provider-http": "0.1.6",
"ethjs-query": "0.3.0",
"ethjs-unit": "0.1.6",
"ethjs-util": "0.1.3",
"js-sha3": "0.5.5",
"number-to-bn": "1.7.0"
},
"dependencies": {
"ethjs-format": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/ethjs-format/-/ethjs-format-0.2.3.tgz",
"integrity": "sha1-m9hnyu6CstvtmEYAuzAiDPPLWDA=",
"requires": {
"bn.js": "4.11.6",
"ethjs-schema": "0.1.9",
"ethjs-util": "0.1.3",
"is-hex-prefixed": "1.0.0",
"number-to-bn": "1.7.0",
"strip-hex-prefix": "1.0.0"
}
},
"ethjs-query": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/ethjs-query/-/ethjs-query-0.3.0.tgz",
"integrity": "sha1-CAmNYQ+BvV+VSnpXq0mJ9+mBX8Q=",
"requires": {
"ethjs-format": "0.2.3",
"ethjs-rpc": "0.1.5"
}
},
"ethjs-schema": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/ethjs-schema/-/ethjs-schema-0.1.9.tgz",
"integrity": "sha1-hYwqXacGrgSBK0zosetLSSHjMJI="
},
"ethjs-util": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.3.tgz",
"integrity": "sha1-39XqSkANxeQhqInK9H4IGtp4u1U=",
"requires": {
"is-hex-prefixed": "1.0.0",
"strip-hex-prefix": "1.0.0"
}
}
}
},
"ethjs-format": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/ethjs-format/-/ethjs-format-0.2.2.tgz",
@ -5952,6 +6020,11 @@
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/ethjs-schema/-/ethjs-schema-0.1.5.tgz",
"integrity": "sha1-WXQOOzl3vNu5sRvDBoIB6Kzquw0="
},
"js-sha3": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.5.tgz",
"integrity": "sha1-uvDA6MVK1ZA0R9+Wreekobynmko="
}
}
},
@ -6134,16 +6207,16 @@
"integrity": "sha1-x7kULEtZUJsziiBLYyiupA3Txk4="
},
"ethjs": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/ethjs/-/ethjs-0.2.9.tgz",
"integrity": "sha1-yagNR7ydVg9Z53gEnSIlXlgfMSs=",
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/ethjs/-/ethjs-0.3.6.tgz",
"integrity": "sha512-9ojnSkV5XXSM5vo0pKgZpE+SNBPxqSUN0dZmMP5dBZVFOYctRd9tfaZ80Jnde3M4JrfUhhkbG5QFvewitaAY7Q==",
"requires": {
"bn.js": "4.11.6",
"ethjs-abi": "0.2.0",
"ethjs-abi": "0.2.1",
"ethjs-contract": "0.1.9",
"ethjs-filter": "0.1.5",
"ethjs-provider-http": "0.1.6",
"ethjs-query": "0.3.0",
"ethjs-query": "0.3.4",
"ethjs-unit": "0.1.6",
"ethjs-util": "0.1.3",
"js-sha3": "0.5.5",
@ -6155,13 +6228,14 @@
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz",
"integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU="
},
"ethjs-query": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/ethjs-query/-/ethjs-query-0.3.0.tgz",
"integrity": "sha1-CAmNYQ+BvV+VSnpXq0mJ9+mBX8Q=",
"ethjs-abi": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/ethjs-abi/-/ethjs-abi-0.2.1.tgz",
"integrity": "sha1-4KepOn6BFjqUR3utVu3lJKtt5TM=",
"requires": {
"ethjs-format": "0.2.3",
"ethjs-rpc": "0.1.5"
"bn.js": "4.11.6",
"js-sha3": "0.5.5",
"number-to-bn": "1.7.0"
}
},
"ethjs-util": {
@ -6288,35 +6362,6 @@
"resolved": "https://registry.npmjs.org/ethjs-filter/-/ethjs-filter-0.1.5.tgz",
"integrity": "sha1-ARKvYBfCRnfjK4/esg5hlgGbdZg="
},
"ethjs-format": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/ethjs-format/-/ethjs-format-0.2.3.tgz",
"integrity": "sha1-m9hnyu6CstvtmEYAuzAiDPPLWDA=",
"requires": {
"bn.js": "4.11.6",
"ethjs-schema": "0.1.9",
"ethjs-util": "0.1.3",
"is-hex-prefixed": "1.0.0",
"number-to-bn": "1.7.0",
"strip-hex-prefix": "1.0.0"
},
"dependencies": {
"bn.js": {
"version": "4.11.6",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz",
"integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU="
},
"ethjs-util": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.3.tgz",
"integrity": "sha1-39XqSkANxeQhqInK9H4IGtp4u1U=",
"requires": {
"is-hex-prefixed": "1.0.0",
"strip-hex-prefix": "1.0.0"
}
}
}
},
"ethjs-provider-http": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/ethjs-provider-http/-/ethjs-provider-http-0.1.6.tgz",
@ -6378,11 +6423,6 @@
"resolved": "https://registry.npmjs.org/ethjs-rpc/-/ethjs-rpc-0.1.5.tgz",
"integrity": "sha1-CZ4i8n3EwYtpeKSF/DaxsPeWkIA="
},
"ethjs-schema": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/ethjs-schema/-/ethjs-schema-0.1.9.tgz",
"integrity": "sha1-hYwqXacGrgSBK0zosetLSSHjMJI="
},
"ethjs-unit": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz",
@ -9370,15 +9410,336 @@
}
},
"gulp-sass": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/gulp-sass/-/gulp-sass-3.1.0.tgz",
"integrity": "sha1-U9xLaKH13f5EJKtMJHZVJpqLdLc=",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/gulp-sass/-/gulp-sass-4.0.1.tgz",
"integrity": "sha512-OMQEgWNggpog8Tc5v1MuI6eo+5iiPkVeLL76iBhDoEEScLUPfZlpvzmgTnLkpcqdrNodZxpz5qcv6mS2rulk3g==",
"requires": {
"gulp-util": "3.0.8",
"chalk": "2.3.2",
"lodash.clonedeep": "4.5.0",
"node-sass": "4.7.2",
"node-sass": "4.8.3",
"plugin-error": "1.0.1",
"replace-ext": "1.0.0",
"strip-ansi": "4.0.0",
"through2": "2.0.3",
"vinyl-sourcemaps-apply": "0.2.1"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"requires": {
"color-convert": "1.9.1"
}
},
"arr-diff": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
"integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA="
},
"assert-plus": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
"integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ="
},
"aws-sign2": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
"integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8="
},
"boom": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
"integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
"requires": {
"hoek": "2.16.3"
}
},
"caseless": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
"integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c="
},
"chalk": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
"integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.3.0"
}
},
"cross-spawn": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
"integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
"requires": {
"lru-cache": "4.1.1",
"which": "1.3.0"
}
},
"cryptiles": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
"integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
"requires": {
"boom": "2.10.1"
}
},
"extend-shallow": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
"integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
"requires": {
"assign-symbols": "1.0.0",
"is-extendable": "1.0.1"
}
},
"form-data": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
"integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=",
"requires": {
"asynckit": "0.4.0",
"combined-stream": "1.0.5",
"mime-types": "2.1.17"
}
},
"get-stdin": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
"integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4="
},
"har-validator": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
"integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=",
"requires": {
"chalk": "1.1.3",
"commander": "2.11.0",
"is-my-json-valid": "2.17.2",
"pinkie-promise": "2.0.1"
},
"dependencies": {
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"requires": {
"ansi-styles": "2.2.1",
"escape-string-regexp": "1.0.5",
"has-ansi": "2.0.0",
"strip-ansi": "3.0.1",
"supports-color": "2.0.0"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": {
"ansi-regex": "2.1.1"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
}
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"hawk": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
"integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
"requires": {
"boom": "2.10.1",
"cryptiles": "2.0.5",
"hoek": "2.16.3",
"sntp": "1.0.9"
}
},
"hoek": {
"version": "2.16.3",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
},
"http-signature": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
"integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
"requires": {
"assert-plus": "0.2.0",
"jsprim": "1.4.1",
"sshpk": "1.13.1"
}
},
"is-extendable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
"integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
"requires": {
"is-plain-object": "2.0.4"
}
},
"nan": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
},
"node-sass": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.8.3.tgz",
"integrity": "sha512-tfFWhUsCk/Y19zarDcPo5xpj+IW3qCfOjVdHtYeG6S1CKbQOh1zqylnQK6cV3z9k80yxAnFX9Y+a9+XysDhhfg==",
"requires": {
"async-foreach": "0.1.3",
"chalk": "1.1.3",
"cross-spawn": "3.0.1",
"gaze": "1.1.2",
"get-stdin": "4.0.1",
"glob": "7.1.2",
"in-publish": "2.0.0",
"lodash.assign": "4.2.0",
"lodash.clonedeep": "4.5.0",
"lodash.mergewith": "4.6.1",
"meow": "3.7.0",
"mkdirp": "0.5.1",
"nan": "2.10.0",
"node-gyp": "3.6.2",
"npmlog": "4.1.2",
"request": "2.79.0",
"sass-graph": "2.2.4",
"stdout-stream": "1.4.0",
"true-case-path": "1.0.2"
},
"dependencies": {
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"requires": {
"ansi-styles": "2.2.1",
"escape-string-regexp": "1.0.5",
"has-ansi": "2.0.0",
"strip-ansi": "3.0.1",
"supports-color": "2.0.0"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": {
"ansi-regex": "2.1.1"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
}
}
},
"plugin-error": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz",
"integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==",
"requires": {
"ansi-colors": "1.0.1",
"arr-diff": "4.0.0",
"arr-union": "3.1.0",
"extend-shallow": "3.0.2"
}
},
"qs": {
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz",
"integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw="
},
"request": {
"version": "2.79.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz",
"integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=",
"requires": {
"aws-sign2": "0.6.0",
"aws4": "1.6.0",
"caseless": "0.11.0",
"combined-stream": "1.0.5",
"extend": "3.0.1",
"forever-agent": "0.6.1",
"form-data": "2.1.4",
"har-validator": "2.0.6",
"hawk": "3.1.3",
"http-signature": "1.1.1",
"is-typedarray": "1.0.0",
"isstream": "0.1.2",
"json-stringify-safe": "5.0.1",
"mime-types": "2.1.17",
"oauth-sign": "0.8.2",
"qs": "6.3.2",
"stringstream": "0.0.5",
"tough-cookie": "2.3.3",
"tunnel-agent": "0.4.3",
"uuid": "3.2.1"
}
},
"sntp": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
"integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
"requires": {
"hoek": "2.16.3"
}
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"requires": {
"ansi-regex": "3.0.0"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
}
}
},
"supports-color": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
"integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==",
"requires": {
"has-flag": "3.0.0"
}
},
"tunnel-agent": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
"integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us="
},
"uuid": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz",
"integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA=="
}
}
},
"gulp-sourcemaps": {
@ -11652,6 +12013,15 @@
"esprima": "4.0.0"
}
},
"js2xmlparser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-3.0.0.tgz",
"integrity": "sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=",
"dev": true,
"requires": {
"xmlcreate": "1.0.2"
}
},
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
@ -11758,6 +12128,49 @@
}
}
},
"jsdoc": {
"version": "3.5.5",
"resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.5.5.tgz",
"integrity": "sha512-6PxB65TAU4WO0Wzyr/4/YhlGovXl0EVYfpKbpSroSj0qBxT4/xod/l40Opkm38dRHRdQgdeY836M0uVnJQG7kg==",
"dev": true,
"requires": {
"babylon": "7.0.0-beta.19",
"bluebird": "3.5.1",
"catharsis": "0.8.9",
"escape-string-regexp": "1.0.5",
"js2xmlparser": "3.0.0",
"klaw": "2.0.0",
"marked": "0.3.19",
"mkdirp": "0.5.1",
"requizzle": "0.2.1",
"strip-json-comments": "2.0.1",
"taffydb": "2.6.2",
"underscore": "1.8.3"
},
"dependencies": {
"babylon": {
"version": "7.0.0-beta.19",
"resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.19.tgz",
"integrity": "sha512-Vg0C9s/REX6/WIXN37UKpv5ZhRi6A4pjHlpkE34+8/a6c2W1Q692n3hmc+SZG5lKRnaExLUbxtJ1SVT+KaCQ/A==",
"dev": true
},
"klaw": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/klaw/-/klaw-2.0.0.tgz",
"integrity": "sha1-WcEo4Nxc5BAgEVEZTuucv4WGUPY=",
"dev": true,
"requires": {
"graceful-fs": "4.1.11"
}
},
"underscore": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=",
"dev": true
}
}
},
"jsdom": {
"version": "11.5.1",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.5.1.tgz",
@ -13086,6 +13499,11 @@
"object-assign": "4.1.1"
}
},
"locale-currency": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/locale-currency/-/locale-currency-0.0.1.tgz",
"integrity": "sha1-yeFaIv9XW0tLuUekv5KsI2vR/ps="
},
"locate-path": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
@ -13837,6 +14255,12 @@
"resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.1.tgz",
"integrity": "sha1-GZTfLTr0gR3lmmcUk0wrIpJzRRg="
},
"marked": {
"version": "0.3.19",
"resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz",
"integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==",
"dev": true
},
"matchdep": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz",
@ -14435,9 +14859,9 @@
}
},
"metamask-logo": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/metamask-logo/-/metamask-logo-2.1.3.tgz",
"integrity": "sha1-F1zleuUMc0Szsdwy0v0LCOOXj9A=",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/metamask-logo/-/metamask-logo-2.1.4.tgz",
"integrity": "sha512-hg/FzMfijpzGgLdZWH+KJKS56cRYaMEwcOq8UcnL/MznpgK4OMlJEaIfO8lg7P2F4Z74Ki+ulrTrFW6jf9L2bw==",
"requires": {
"gl-mat4": "1.1.4",
"gl-vec3": "1.0.3"
@ -15182,6 +15606,7 @@
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.7.2.tgz",
"integrity": "sha512-CaV+wLqZ7//Jdom5aUFCpGNoECd7BbNhjuwdsX/LkXBrHl8eb1Wjw4HvWqcFvhr5KuNgAk8i/myf/MQ1YYeroA==",
"dev": true,
"requires": {
"async-foreach": "0.1.3",
"chalk": "1.1.3",
@ -15207,17 +15632,20 @@
"assert-plus": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
"integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ="
"integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
"dev": true
},
"aws-sign2": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
"integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8="
"integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
"dev": true
},
"boom": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
"integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
"dev": true,
"requires": {
"hoek": "2.16.3"
}
@ -15225,12 +15653,14 @@
"caseless": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
"integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c="
"integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=",
"dev": true
},
"cross-spawn": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
"integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
"dev": true,
"requires": {
"lru-cache": "4.1.1",
"which": "1.3.0"
@ -15240,6 +15670,7 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
"integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
"dev": true,
"requires": {
"boom": "2.10.1"
}
@ -15248,6 +15679,7 @@
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
"integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=",
"dev": true,
"requires": {
"asynckit": "0.4.0",
"combined-stream": "1.0.5",
@ -15257,12 +15689,14 @@
"get-stdin": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
"integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4="
"integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
"dev": true
},
"har-validator": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
"integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=",
"dev": true,
"requires": {
"chalk": "1.1.3",
"commander": "2.11.0",
@ -15274,6 +15708,7 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
"integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
"dev": true,
"requires": {
"boom": "2.10.1",
"cryptiles": "2.0.5",
@ -15284,12 +15719,14 @@
"hoek": {
"version": "2.16.3",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=",
"dev": true
},
"http-signature": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
"integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
"dev": true,
"requires": {
"assert-plus": "0.2.0",
"jsprim": "1.4.1",
@ -15299,12 +15736,14 @@
"qs": {
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz",
"integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw="
"integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=",
"dev": true
},
"request": {
"version": "2.79.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz",
"integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=",
"dev": true,
"requires": {
"aws-sign2": "0.6.0",
"aws4": "1.6.0",
@ -15332,6 +15771,7 @@
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
"integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
"dev": true,
"requires": {
"hoek": "2.16.3"
}
@ -15339,12 +15779,14 @@
"tunnel-agent": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
"integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us="
"integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=",
"dev": true
},
"uuid": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz",
"integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA=="
"integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==",
"dev": true
}
}
},
@ -19207,6 +19649,12 @@
}
}
},
"radgrad-jsdoc-template": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/radgrad-jsdoc-template/-/radgrad-jsdoc-template-1.1.3.tgz",
"integrity": "sha512-yk1XB6NlrP6JIr3xHzCrNwCmkONNgqM+sZxFAdmTU/CsaT7N/lns2/Wfa048li3wrao5b6KwYrdu6KgiGh9nIQ==",
"dev": true
},
"raf": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz",
@ -19304,9 +19752,9 @@
}
},
"raven-js": {
"version": "3.24.0",
"resolved": "https://registry.npmjs.org/raven-js/-/raven-js-3.24.0.tgz",
"integrity": "sha512-+/ygcWib8PXAE7Xq53j1tYxCgkzFyp9z05LYAKp2PA9KwO4Ek74q1tkGwZyPWI/FoXOgas6jNtQ7O3tdPif6uA=="
"version": "3.24.2",
"resolved": "https://registry.npmjs.org/raven-js/-/raven-js-3.24.2.tgz",
"integrity": "sha512-Dy/FHDxuo5pXywVf8Nrs5utB2juMATpkxWGqHjVbpFD3m8CaWYLvkB5SEXjWFUZ5GvUsrBVVQ+Dfcp0x6Z7SOg=="
},
"raw-body": {
"version": "2.3.2",
@ -20065,6 +20513,23 @@
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
"requizzle": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.1.tgz",
"integrity": "sha1-aUPDUwxNmn5G8c3dUcFY/GcM294=",
"dev": true,
"requires": {
"underscore": "1.6.0"
},
"dependencies": {
"underscore": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz",
"integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=",
"dev": true
}
}
},
"resolve": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz",
@ -22612,6 +23077,12 @@
}
}
},
"taffydb": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz",
"integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=",
"dev": true
},
"tap-parser": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-5.4.0.tgz",
@ -23404,6 +23875,23 @@
"integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=",
"dev": true
},
"underscore-contrib": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/underscore-contrib/-/underscore-contrib-0.3.0.tgz",
"integrity": "sha1-ZltmwkeD+PorGMn4y7Dix9SMJsc=",
"dev": true,
"requires": {
"underscore": "1.6.0"
},
"dependencies": {
"underscore": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz",
"integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=",
"dev": true
}
}
},
"undertaker": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.0.tgz",
@ -25070,6 +25558,12 @@
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=",
"dev": true
},
"xmlcreate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz",
"integrity": "sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=",
"dev": true
},
"xmldom": {
"version": "0.1.27",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz",

View File

@ -7,8 +7,9 @@
"start": "gulp dev:extension",
"mascara": "gulp dev:mascara & node ./mascara/example/server",
"dist": "gulp dist",
"doc": "jsdoc -c development/tools/.jsdoc.json",
"test": "npm run test:unit && npm run test:integration && npm run lint",
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require babel-core/register --require test/helper.js --recursive \"test/unit/**/*.js\"",
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\"",
"test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js",
"test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara",
"test:integration:build": "gulp build:scss",
@ -75,6 +76,7 @@
"classnames": "^2.2.5",
"clone": "^2.1.1",
"copy-to-clipboard": "^3.0.8",
"currency-formatter": "^1.4.2",
"debounce": "^1.0.0",
"debounce-stream": "^2.0.0",
"deep-extend": "^0.5.0",
@ -90,7 +92,7 @@
"eth-hd-keyring": "^1.2.1",
"eth-json-rpc-filters": "^1.2.5",
"eth-json-rpc-infura": "^3.0.0",
"eth-keyring-controller": "^2.1.4",
"eth-keyring-controller": "^2.2.0",
"eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2",
"eth-sig-util": "^1.4.2",
@ -101,7 +103,7 @@
"ethereumjs-wallet": "^0.6.0",
"etherscan-link": "^1.0.2",
"ethjs": "^0.3.4",
"ethjs-contract": "^0.1.9",
"ethjs-contract": "^0.2.0",
"ethjs-ens": "^2.0.0",
"ethjs-query": "^0.3.4",
"express": "^4.15.5",
@ -150,7 +152,7 @@
"pumpify": "^1.3.4",
"qrcode-npm": "0.0.3",
"ramda": "^0.24.1",
"raven-js": "^3.24.0",
"raven-js": "^3.24.2",
"react": "^15.6.2",
"react-addons-css-transition-group": "^15.6.0",
"react-dom": "^15.6.2",
@ -236,6 +238,7 @@
"gulp-zip": "^4.0.0",
"image-size": "^0.6.2",
"isomorphic-fetch": "^2.2.1",
"jsdoc": "^3.5.5",
"jsdom": "^11.2.0",
"jsdom-global": "^3.0.2",
"jshint-stylish": "~2.2.1",
@ -257,6 +260,7 @@
"prompt": "^1.0.0",
"qs": "^6.2.0",
"qunitjs": "^2.4.1",
"radgrad-jsdoc-template": "^1.1.3",
"react-addons-test-utils": "^15.5.1",
"react-test-renderer": "^15.6.2",
"react-testutils-additions": "^15.2.0",
@ -274,6 +278,6 @@
"watchify": "^3.9.0"
},
"engines": {
"node": ">=0.8.0"
"node": ">=8.0.0"
}
}

View File

@ -0,0 +1,28 @@
const reactTriggerChange = require('../../lib/react-trigger-change')
const {
timeout,
queryAsync,
findAsync,
} = require('../../lib/util')
QUnit.module('currency localization')
QUnit.test('renders localized currency', (assert) => {
const done = assert.async()
runCurrencyLocalizationTest(assert).then(done).catch((err) => {
assert.notOk(err, `Error was thrown: ${err.stack}`)
done()
})
})
async function runCurrencyLocalizationTest(assert, done) {
console.log('*** start runCurrencyLocalizationTest')
const selectState = await queryAsync($, 'select')
selectState.val('currency localization')
reactTriggerChange(selectState[0])
await timeout(1000)
const txView = await queryAsync($, '.tx-view')
const heroBalance = await findAsync($(txView), '.hero-balance')
const fiatAmount = await findAsync($(heroBalance), '.fiat-amount')
assert.equal(fiatAmount[0].textContent, '₱102,707.97')
}

View File

@ -91,7 +91,7 @@ async function runSendFlowTest(assert, done) {
)
assert.equal(
sendGasField.find('.currency-display__converted-value')[0].textContent,
'0.24 USD',
'$0.24 USD',
'send gas field should show estimated gas total converted to USD'
)
@ -118,7 +118,7 @@ async function runSendFlowTest(assert, done) {
)
assert.equal(
(await findAsync(sendGasField, '.currency-display__converted-value'))[0].textContent,
'3.60 USD',
'$3.60 USD',
'send gas field should show customized gas total converted to USD'
)
@ -138,9 +138,9 @@ async function runSendFlowTest(assert, done) {
const confirmScreenRows = await queryAsync($, '.confirm-screen-rows')
const confirmScreenGas = confirmScreenRows.find('.currency-display__converted-value')[0]
assert.equal(confirmScreenGas.textContent, '3.60 USD', 'confirm screen should show correct gas')
assert.equal(confirmScreenGas.textContent, '$3.60 USD', 'confirm screen should show correct gas')
const confirmScreenTotal = confirmScreenRows.find('.confirm-screen-row-info')[2]
assert.equal(confirmScreenTotal.textContent, '2405.36 USD', 'confirm screen should show correct total')
assert.equal(confirmScreenTotal.textContent, '$2,405.36 USD', 'confirm screen should show correct total')
const confirmScreenBackButton = await queryAsync($, '.page-container__back-button')
confirmScreenBackButton[0].click()

View File

@ -53,7 +53,7 @@ async function runTxListItemsTest(assert, done) {
const confirmedTokenTx = txListItems[6]
const confirmedTokenTxAddress = await findAsync($(confirmedTokenTx), '.tx-list-account')
assert.equal(confirmedTokenTxAddress[0].textContent, '0xe7884118...81a9', 'confirmedTokenTx has correct address')
assert.equal(confirmedTokenTxAddress[0].textContent, '0xE7884118...81a9', 'confirmedTokenTx has correct address')
const rejectedTx = txListItems[7]
const rejectedTxRenderedStatus = await findAsync($(rejectedTx), '.tx-list-status')

5
test/setup.js Normal file
View File

@ -0,0 +1,5 @@
require('babel-register')({
ignore: name => name.includes('node_modules') && !name.includes('obs-store'),
})
require('./helper')

View File

@ -0,0 +1,35 @@
const assert = require('assert')
const ComposableObservableStore = require('../../app/scripts/lib/ComposableObservableStore')
const ObservableStore = require('obs-store')
describe('ComposableObservableStore', () => {
it('should register initial state', () => {
const store = new ComposableObservableStore('state')
assert.strictEqual(store.getState(), 'state')
})
it('should register initial structure', () => {
const testStore = new ObservableStore()
const store = new ComposableObservableStore(null, { TestStore: testStore })
testStore.putState('state')
assert.deepEqual(store.getState(), { TestStore: 'state' })
})
it('should update structure', () => {
const testStore = new ObservableStore()
const store = new ComposableObservableStore()
store.updateStructure({ TestStore: testStore })
testStore.putState('state')
assert.deepEqual(store.getState(), { TestStore: 'state' })
})
it('should return flattened state', () => {
const fooStore = new ObservableStore({ foo: 'foo' })
const barStore = new ObservableStore({ bar: 'bar' })
const store = new ComposableObservableStore(null, {
FooStore: fooStore,
BarStore: barStore,
})
assert.deepEqual(store.getFlatState(), { foo: 'foo', bar: 'bar' })
})
})

View File

@ -0,0 +1,27 @@
const assert = require('assert')
const currencyFormatter = require('currency-formatter')
const infuraConversion = require('../../ui/app/infura-conversion.json')
describe('currencyFormatting', function () {
it('be able to format any infura currency', function (done) {
const number = 10000
infuraConversion.objects.forEach((conversion) => {
const code = conversion.quote.code.toUpperCase()
const result = currencyFormatter.format(number, { code })
switch (code) {
case 'USD':
assert.equal(result, '$10,000.00')
break
case 'JPY':
assert.equal(result, '¥10,000')
break
default:
assert.ok(result, `Currency ${code} formatted as ${result}`)
}
})
done()
})
})

View File

@ -1,6 +1,10 @@
const assert = require('assert')
const nock = require('nock')
const NetworkController = require('../../app/scripts/controllers/network')
const {
getNetworkDisplayName,
getNetworkEndpoints,
} = require('../../app/scripts/controllers/network/util')
const { createTestProviderTools } = require('../stub/provider')
const providerResultStub = {}
@ -80,3 +84,39 @@ describe('# Network Controller', function () {
})
})
})
describe('# Network utils', () => {
it('getNetworkDisplayName should return the correct network name', () => {
const tests = [
{
input: 3,
expected: 'Ropsten',
}, {
input: 4,
expected: 'Rinkeby',
}, {
input: 42,
expected: 'Kovan',
}, {
input: 'ropsten',
expected: 'Ropsten',
}, {
input: 'rinkeby',
expected: 'Rinkeby',
}, {
input: 'kovan',
expected: 'Kovan',
}, {
input: 'mainnet',
expected: 'Main Ethereum Network',
},
]
tests.forEach(({ input, expected }) => assert.equal(getNetworkDisplayName(input), expected))
})
it('getNetworkEndpoints should return the correct endpoints', () => {
assert.equal(getNetworkEndpoints('networkBeta').ropsten, 'https://ropsten.infura.io/metamask2')
assert.equal(getNetworkEndpoints('network').rinkeby, 'https://rinkeby.infura.io/metamask')
})
})

View File

@ -0,0 +1,29 @@
const assert = require('assert')
const sinon = require('sinon')
const TokenRatesController = require('../../app/scripts/controllers/token-rates')
const ObservableStore = require('obs-store')
describe('TokenRatesController', () => {
it('should listen for preferences store updates', () => {
const preferences = new ObservableStore({ tokens: [] })
const controller = new TokenRatesController({ preferences })
preferences.putState({ tokens: ['foo'] })
assert.deepEqual(controller._tokens, ['foo'])
})
it('should poll on correct interval', async () => {
const stub = sinon.stub(global, 'setInterval')
new TokenRatesController({ interval: 1337 }) // eslint-disable-line no-new
assert.strictEqual(stub.getCall(0).args[1], 1337)
stub.restore()
})
it('should fetch each token rate based on address', async () => {
const controller = new TokenRatesController()
controller.isActive = true
controller.fetchExchangeRate = address => address
controller.tokens = [{ address: 'foo' }, { address: 'bar' }]
await controller.updateExchangeRates()
assert.deepEqual(controller.store.getState().contractExchangeRates, { foo: 'foo', bar: 'bar' })
})
})

View File

@ -3,6 +3,7 @@ const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url')
const { getTokenAddressFromTokenObject } = require('./util')
const ethUtil = require('ethereumjs-util')
const { fetchLocale } = require('../i18n-helper')
const log = require('loglevel')
var actions = {
_setBackgroundConnection: _setBackgroundConnection,
@ -222,8 +223,6 @@ var actions = {
coinBaseSubview: coinBaseSubview,
SHAPESHIFT_SUBVIEW: 'SHAPESHIFT_SUBVIEW',
shapeShiftSubview: shapeShiftSubview,
UPDATE_TOKEN_EXCHANGE_RATE: 'UPDATE_TOKEN_EXCHANGE_RATE',
updateTokenExchangeRate,
PAIR_UPDATE: 'PAIR_UPDATE',
pairUpdate: pairUpdate,
coinShiftRquest: coinShiftRquest,
@ -348,13 +347,11 @@ function transitionBackward () {
}
}
function confirmSeedWords () {
function clearSeedWordCache () {
log.debug(`background.clearSeedWordCache`)
return dispatch => {
dispatch(actions.showLoadingIndication())
log.debug(`background.clearSeedWordCache`)
return new Promise((resolve, reject) => {
background.clearSeedWordCache((err, account) => {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
@ -368,6 +365,22 @@ function confirmSeedWords () {
}
}
function confirmSeedWords () {
return async dispatch => {
dispatch(actions.showLoadingIndication())
const account = await dispatch(clearSeedWordCache())
return dispatch(setIsRevealingSeedWords(false))
.then(() => {
dispatch(actions.hideLoadingIndication())
return account
})
.catch(() => {
dispatch(actions.hideLoadingIndication())
return account
})
}
}
function createNewVaultAndRestore (password, seed) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
@ -449,11 +462,13 @@ function requestRevealSeed (password) {
}
dispatch(actions.showNewVaultSeed(result))
dispatch(actions.hideLoadingIndication())
resolve()
})
})
})
.then(() => dispatch(setIsRevealingSeedWords(true)))
.then(() => dispatch(actions.hideLoadingIndication()))
.catch(() => dispatch(actions.hideLoadingIndication()))
}
}
@ -1753,28 +1768,6 @@ function shapeShiftRequest (query, options, cb) {
}
}
function updateTokenExchangeRate (token = '') {
const pair = `${token.toLowerCase()}_eth`
return dispatch => {
if (!token) {
return
}
shapeShiftRequest('marketinfo', { pair }, marketinfo => {
if (!marketinfo.error) {
dispatch({
type: actions.UPDATE_TOKEN_EXCHANGE_RATE,
payload: {
pair,
marketinfo,
},
})
}
})
}
}
function setFeatureFlag (feature, activated, notificationType) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
@ -1932,3 +1925,11 @@ function updateNetworkEndpointType (networkEndpointType) {
value: networkEndpointType,
}
}
function setIsRevealingSeedWords (reveal) {
return dispatch => {
log.debug(`background.setIsRevealingSeedWords`)
background.setIsRevealingSeedWords(reveal)
return forceUpdateMetamaskState(dispatch)
}
}

View File

@ -6,6 +6,7 @@ const { compose } = require('recompose')
const h = require('react-hyperscript')
const actions = require('./actions')
const classnames = require('classnames')
const log = require('loglevel')
// init
const InitializeScreen = require('../../mascara/src/app/first-time').default
@ -55,11 +56,20 @@ const {
class App extends Component {
componentWillMount () {
const { currentCurrency, setCurrentCurrencyToUSD } = this.props
const {
currentCurrency,
setCurrentCurrencyToUSD,
isRevealingSeedWords,
clearSeedWords,
} = this.props
if (!currentCurrency) {
setCurrentCurrencyToUSD()
}
if (isRevealingSeedWords) {
clearSeedWords()
}
}
renderRoutes () {
@ -136,8 +146,6 @@ class App extends Component {
loadingMessage: loadMessage,
}),
// this.renderLoadingIndicator({ isLoading, isLoadingNetwork, loadMessage }),
// content
this.renderRoutes(),
])
@ -301,17 +309,6 @@ class App extends Component {
)
}
renderLoadingIndicator ({ isLoading, isLoadingNetwork, loadMessage }) {
const { isMascara } = this.props
return isMascara
? null
: h(Loading, {
isLoading: isLoading || isLoadingNetwork,
loadingMessage: loadMessage,
})
}
toggleMetamaskActive () {
if (!this.props.isUnlocked) {
// currently inactive: redirect to password box
@ -405,6 +402,8 @@ App.propTypes = {
isMouseUser: PropTypes.bool,
setMouseUserState: PropTypes.func,
t: PropTypes.func,
isRevealingSeedWords: PropTypes.bool,
clearSeedWords: PropTypes.func,
}
function mapStateToProps (state) {
@ -485,6 +484,7 @@ function mapDispatchToProps (dispatch, ownProps) {
setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')),
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)),
clearSeedWords: () => dispatch(actions.confirmSeedWords()),
}
}

View File

@ -7,8 +7,8 @@ const connect = require('react-redux').connect
const Dropdown = require('./dropdown').Dropdown
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
const Identicon = require('./identicon')
const ethUtil = require('ethereumjs-util')
const copyToClipboard = require('copy-to-clipboard')
const { checksumAddress } = require('../util')
class AccountDropdowns extends Component {
constructor (props) {
@ -212,8 +212,7 @@ class AccountDropdowns extends Component {
closeMenu: () => {},
onClick: () => {
const { selected } = this.props
const checkSumAddress = selected && ethUtil.toChecksumAddress(selected)
copyToClipboard(checkSumAddress)
copyToClipboard(checksumAddress(selected))
},
},
this.context.t('copyAddress'),

View File

@ -4,6 +4,8 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const TokenBalance = require('./token-balance')
const Identicon = require('./identicon')
const currencyFormatter = require('currency-formatter')
const currencies = require('currency-formatter/currencies')
const { formatBalance, generateBalanceObject } = require('../util')
@ -97,9 +99,17 @@ BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatS
const shouldNotRenderFiat = fiatDisplayNumber === 'N/A' || Number(fiatDisplayNumber) === 0
if (shouldNotRenderFiat) return null
const upperCaseFiatSuffix = fiatSuffix.toUpperCase()
const display = currencies.find(currency => currency.code === upperCaseFiatSuffix)
? currencyFormatter.format(Number(fiatDisplayNumber), {
code: upperCaseFiatSuffix,
})
: `${fiatPrefix}${fiatDisplayNumber} ${upperCaseFiatSuffix}`
return h('div.fiat-amount', {
style: {},
}, `${fiatPrefix}${fiatDisplayNumber} ${fiatSuffix}`)
}, display)
}
BalanceComponent.prototype.getTokenBalance = function (formattedBalance, shorten) {
@ -117,5 +127,9 @@ BalanceComponent.prototype.getFiatDisplayNumber = function (formattedBalance, co
const splitBalance = formattedBalance.split(' ')
return (Number(splitBalance[0]) * conversionRate).toFixed(2)
const convertedNumber = (Number(splitBalance[0]) * conversionRate)
const wholePart = Math.floor(convertedNumber)
const decimalPart = convertedNumber - wholePart
return wholePart + Number(decimalPart.toPrecision(2))
}

View File

@ -9,7 +9,7 @@ const ShapeshiftForm = require('./shapeshift-form')
const Loading = require('./loading')
const AccountPanel = require('./account-panel')
const RadioList = require('./custom-radio-list')
const networkNames = require('../../../app/scripts/config.js').networkNames
const { getNetworkDisplayName } = require('../../../app/scripts/controllers/network/util')
BuyButtonSubview.contextTypes = {
t: PropTypes.func,
@ -148,7 +148,7 @@ BuyButtonSubview.prototype.primarySubview = function () {
case '3':
case '4':
case '42':
const networkName = networkNames[network]
const networkName = getNetworkDisplayName(network)
const label = `${networkName} ${this.context.t('testFaucet')}`
return (
h('div.flex-column', {

View File

@ -7,7 +7,7 @@ const connect = require('react-redux').connect
const Dropdown = require('./dropdown').Dropdown
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
const Identicon = require('../../identicon')
const ethUtil = require('ethereumjs-util')
const { checksumAddress } = require('../../../util')
const copyToClipboard = require('copy-to-clipboard')
const { formatBalance } = require('../../../util')
@ -311,8 +311,7 @@ class AccountDropdowns extends Component {
closeMenu: () => {},
onClick: () => {
const { selected } = this.props
const checkSumAddress = selected && ethUtil.toChecksumAddress(selected)
copyToClipboard(checkSumAddress)
copyToClipboard(checksumAddress(selected))
},
style: Object.assign(
dropdownMenuItemStyle,

View File

@ -3,12 +3,14 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const actions = require('../../actions')
const Dropdown = require('./components/dropdown').Dropdown
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
const NetworkDropdownIcon = require('./components/network-dropdown-icon')
const R = require('ramda')
const { SETTINGS_ROUTE } = require('../../routes')
// classes from nodes of the toggle element.
const notToggleElementClassnames = [
@ -41,9 +43,6 @@ function mapDispatchToProps (dispatch) {
setRpcTarget: (target) => {
dispatch(actions.setRpcTarget(target))
},
showConfigPage: () => {
dispatch(actions.showConfigPage())
},
showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),
}
@ -59,7 +58,10 @@ NetworkDropdown.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(NetworkDropdown)
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(NetworkDropdown)
// TODO: specify default props and proptypes
@ -227,7 +229,7 @@ NetworkDropdown.prototype.render = function () {
DropdownMenuItem,
{
closeMenu: () => this.props.hideNetworkDropdown(),
onClick: () => this.props.showConfigPage(),
onClick: () => this.props.history.push(SETTINGS_ROUTE),
style: dropdownMenuItemStyle,
},
[

View File

@ -11,6 +11,7 @@ const ensRE = /.+\..+$/
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
const connect = require('react-redux').connect
const ToAutoComplete = require('./send/to-autocomplete')
const log = require('loglevel')
EnsInput.contextTypes = {
t: PropTypes.func,

View File

@ -1,6 +1,7 @@
const { Component } = require('react')
const h = require('react-hyperscript')
const PropTypes = require('prop-types')
const classnames = require('classnames')
class LoadingIndicator extends Component {
renderMessage () {
@ -10,14 +11,16 @@ class LoadingIndicator extends Component {
render () {
return (
h('.full-flex-height.loading-overlay', {}, [
h('img', {
src: 'images/loading.svg',
}),
h('.loading-overlay', {
className: classnames({ 'loading-overlay--full-screen': this.props.fullScreen }),
}, [
h('.flex-center.flex-column', [
h('img', {
src: 'images/loading.svg',
}),
h('br'),
this.renderMessage(),
this.renderMessage(),
]),
])
)
}
@ -25,6 +28,7 @@ class LoadingIndicator extends Component {
LoadingIndicator.propTypes = {
loadingMessage: PropTypes.string,
fullScreen: PropTypes.bool,
}
module.exports = LoadingIndicator

View File

@ -4,7 +4,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
const networkNames = require('../../../../app/scripts/config.js').networkNames
const { getNetworkDisplayName } = require('../../../../app/scripts/controllers/network/util')
function mapStateToProps (state) {
return {
@ -52,7 +52,7 @@ BuyOptions.prototype.renderModalContentOption = function (title, header, onClick
BuyOptions.prototype.render = function () {
const { network, toCoinbase, address, toFaucet } = this.props
const isTestNetwork = ['3', '4', '42'].find(n => n === network)
const networkName = networkNames[network]
const networkName = getNetworkDisplayName(network)
return h('div', {}, [
h('div.buy-modal-content.transfers-subview', {

View File

@ -4,7 +4,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
const networkNames = require('../../../../app/scripts/config.js').networkNames
const { getNetworkDisplayName } = require('../../../../app/scripts/controllers/network/util')
const ShapeshiftForm = require('../shapeshift-form')
let DIRECT_DEPOSIT_ROW_TITLE
@ -122,7 +122,7 @@ DepositEtherModal.prototype.render = function () {
const { buyingWithShapeshift } = this.state
const isTestNetwork = ['3', '4', '42'].find(n => n === network)
const networkName = networkNames[network]
const networkName = getNetworkDisplayName(network)
return h('div.page-container.page-container--full-width.page-container--full-height', {}, [

View File

@ -3,12 +3,13 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const ethUtil = require('ethereumjs-util')
const { stripHexPrefix } = require('ethereumjs-util')
const actions = require('../../actions')
const AccountModalContainer = require('./account-modal-container')
const { getSelectedIdentity } = require('../../selectors')
const ReadOnlyInput = require('../readonly-input')
const copyToClipboard = require('copy-to-clipboard')
const { checksumAddress } = require('../../util')
function mapStateToProps (state) {
return {
@ -60,7 +61,7 @@ ExportPrivateKeyModal.prototype.renderPasswordLabel = function (privateKey) {
}
ExportPrivateKeyModal.prototype.renderPasswordInput = function (privateKey) {
const plainKey = privateKey && ethUtil.stripHexPrefix(privateKey)
const plainKey = privateKey && stripHexPrefix(privateKey)
return privateKey
? h(ReadOnlyInput, {
@ -121,7 +122,7 @@ ExportPrivateKeyModal.prototype.render = function () {
h(ReadOnlyInput, {
wrapperClass: 'ellip-address-wrapper',
inputClass: 'qr-ellip-address ellip-address',
value: address,
value: checksumAddress(address),
}),
h('div.account-modal-divider'),

View File

@ -5,7 +5,8 @@ const connect = require('react-redux').connect
const FadeModal = require('boron').FadeModal
const actions = require('../../actions')
const isMobileView = require('../../../lib/is-mobile-view')
const isPopupOrNotification = require('../../../../app/scripts/lib/is-popup-or-notification')
const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
// Modal Components
const BuyOptions = require('./buy-options-modal')
@ -162,7 +163,7 @@ const MODALS = {
],
mobileModalStyle: {
width: '95%',
top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
},
laptopModalStyle: {
width: '449px',
@ -179,7 +180,7 @@ const MODALS = {
],
mobileModalStyle: {
width: '95%',
top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
},
laptopModalStyle: {
width: '449px',
@ -196,7 +197,7 @@ const MODALS = {
],
mobileModalStyle: {
width: '95%',
top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
},
laptopModalStyle: {
width: '449px',
@ -208,7 +209,7 @@ const MODALS = {
contents: h(ConfirmResetAccount),
mobileModalStyle: {
width: '95%',
top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
},
laptopModalStyle: {
width: '473px',

View File

@ -5,6 +5,7 @@ const { Redirect, withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const h = require('react-hyperscript')
const actions = require('../../actions')
const log = require('loglevel')
// init
const NewKeyChainScreen = require('../../new-keychain')

View File

@ -6,6 +6,7 @@ const connect = require('../../../metamask-connect')
const h = require('react-hyperscript')
const { createNewVaultAndRestore, unMarkPasswordForgotten } = require('../../../actions')
const { DEFAULT_ROUTE } = require('../../../routes')
const log = require('loglevel')
class RestoreVaultPage extends PersistentForm {
constructor (props) {

View File

@ -3,6 +3,14 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
class Info extends Component {
constructor (props) {
super(props)
this.state = {
version: global.platform.getVersion(),
}
}
renderLogo () {
return (
h('div.settings__info-logo-wrapper', [
@ -76,7 +84,7 @@ class Info extends Component {
this.renderLogo(),
h('div.settings__info-item', [
h('div.settings__info-version-header', 'MetaMask Version'),
h('div.settings__info-version-number', '4.0.0'),
h('div.settings__info-version-number', this.state.version),
]),
h('div.settings__info-item', [
h(

View File

@ -12,7 +12,7 @@ const SimpleDropdown = require('../../dropdowns/simple-dropdown')
const ToggleButton = require('react-toggle-button')
const { REVEAL_SEED_ROUTE } = require('../../../routes')
const locales = require('../../../../../app/_locales/index.json')
const { OLD_UI_NETWORK_TYPE } = require('../../../../../app/scripts/config').enums
const { OLD_UI_NETWORK_TYPE } = require('../../../../../app/scripts/controllers/network/enums')
const getInfuraCurrencyOptions = () => {
const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {

View File

@ -11,11 +11,12 @@ const {
setNetworkEndpoints,
setFeatureFlag,
} = require('../../actions')
const environmentType = require('../../../../app/scripts/lib/environment-type')
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
const getCaretCoordinates = require('textarea-caret')
const EventEmitter = require('events').EventEmitter
const Mascot = require('../mascot')
const { OLD_UI_NETWORK_TYPE } = require('../../../../app/scripts/config').enums
const { OLD_UI_NETWORK_TYPE } = require('../../../../app/scripts/controllers/network/enums')
const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../routes')
class UnlockScreen extends Component {
@ -131,7 +132,7 @@ class UnlockScreen extends Component {
this.props.markPasswordForgotten()
this.props.history.push(RESTORE_VAULT_ROUTE)
if (environmentType() === 'popup') {
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
global.platform.openExtensionInBrowser()
}
},

Some files were not shown because too many files have changed in this diff Show More