Merge branch 'master' into i3621-LogoPerformance

This commit is contained in:
kumavis 2018-04-09 13:56:59 -07:00 committed by GitHub
commit df1f891585
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
98 changed files with 6531 additions and 2035 deletions

View File

@ -4,6 +4,35 @@
- Improved performance of 3D fox logo.
## 4.5.5 Fri Apr 06 2018
- Graceful handling of unknown keys in txParams
- Fixes buggy handling of historical transactions with unknown keys in txParams
- Fix link for 'Learn More' in the Add Token Screen to open to a new tab.
- Fix Download State Logs button [#3791](https://github.com/MetaMask/metamask-extension/issues/3791)
- Enhanced migration error handling + reporting
## 4.5.4 (aborted) Thu Apr 05 2018
- Graceful handling of unknown keys in txParams
- Fix link for 'Learn More' in the Add Token Screen to open to a new tab.
- Fix Download State Logs button [#3791](https://github.com/MetaMask/metamask-extension/issues/3791)
- Fix migration error reporting
## 4.5.3 Wed Apr 04 2018
- Fix bug where checksum address are messing with balance issue [#3843](https://github.com/MetaMask/metamask-extension/issues/3843)
- new ui: fix the confirm transaction screen
## 4.5.2 Wed Apr 04 2018
- Fix overly strict validation where transactions were rejected with hex encoded "chainId"
## 4.5.1 Tue Apr 03 2018
- Fix default network (should be mainnet not Rinkeby)
- Fix Sentry automated error reporting endpoint
## 4.5.0 Mon Apr 02 2018
- (beta ui) Internationalization: Select your preferred language in the settings screen

View File

@ -56,7 +56,7 @@
"message": "Balance:"
},
"balances": {
"message": "Your balances"
"message": "Token balance(s)"
},
"balanceIsInsufficientGas": {
"message": "Insufficient balance for current gas total"

View File

@ -13,6 +13,7 @@
{ "code": "ru", "name": "Russian" },
{ "code": "sl", "name": "Slovenian" },
{ "code": "th", "name": "Thai" },
{ "code": "tr", "name": "Turkish" },
{ "code": "vi", "name": "Vietnamese" },
{ "code": "zh_CN", "name": "Mandarin" },
{ "code": "zh_TW", "name": "Taiwanese" }

View File

@ -0,0 +1,912 @@
{
"accept": {
"message": "Kabul et"
},
"account": {
"message": "Hesap"
},
"accountDetails": {
"message": "Hesap Detayları"
},
"accountName": {
"message": "Hesap İsmi"
},
"address": {
"message": "Adres"
},
"addCustomToken": {
"message": "Özel jeton ekle"
},
"addToken": {
"message": "Jeton ekle"
},
"addTokens": {
"message": "Jetonlar ekle"
},
"amount": {
"message": "Tutar"
},
"amountPlusGas": {
"message": "Tutar + Gas"
},
"appDescription": {
"message": "Ethereum Tarayıcı Uzantısı",
"description": "Uygulama açıklaması"
},
"appName": {
"message": "MetaMask",
"description": "Uygulama ismi"
},
"approved": {
"message": "Onaylandı"
},
"attemptingConnect": {
"message": "Blockchain'e bağlanmayı deniyor"
},
"attributions": {
"message": "Atıflar"
},
"available": {
"message": "Müsait"
},
"back": {
"message": "Geri"
},
"balance": {
"message": "Bakiye:"
},
"balances": {
"message": "Jeton bakiyesi"
},
"balanceIsInsufficientGas": {
"message": "Toplam gas için yetersiz bakiye"
},
"beta": {
"message": "BETA"
},
"betweenMinAndMax": {
"message": "$1'e eşit veya daha büyük olmalı ve $2'den küçük veya eşit olmalı",
"description": "Onaltılık sayının ondalık sayı olarak girişi için yardımcı"
},
"blockiesIdenticon": {
"message": "Blockies Identicon kullan"
},
"borrowDharma": {
"message": "Dharma (Beta) ile ödünç al"
},
"builtInCalifornia": {
"message": "MetaMask California'da tasarlandı ve yaratıldı"
},
"buy": {
"message": "Satın al"
},
"buyCoinbase": {
"message": "Coinbase'de satın al"
},
"buyCoinbaseExplainer": {
"message": "Coinbase bitcoin, ethereum, and litecoin alıp satmanın dünyadaki en popüler yolu"
},
"ok": {
"message": "Tamam"
},
"cancel": {
"message": "Vazgeç"
},
"classicInterface": {
"message": "Klasik arayüzü kullan"
},
"clickCopy": {
"message": "Kopyalamak için tıkla"
},
"confirm": {
"message": "Onayla"
},
"confirmed": {
"message": "Onaylandı"
},
"confirmContract": {
"message": "Sözleşmeyi onayla"
},
"confirmPassword": {
"message": "Şifreyi onayla"
},
"confirmTransaction": {
"message": "İşlemi onayla"
},
"continue": {
"message": "Devam et"
},
"continueToCoinbase": {
"message": "Coinbase'e devam et"
},
"contractDeployment": {
"message": "Sözleşme kurulumu"
},
"conversionProgress": {
"message": "Çevirim devam ediyor"
},
"copiedButton": {
"message": "Kopyalandı"
},
"copiedClipboard": {
"message": "Panoya kopyalandı"
},
"copiedExclamation": {
"message": "Kopyalandı!"
},
"copiedSafe": {
"message": "Güvenli bir yere kopyaladım"
},
"copy": {
"message": "Kopyala"
},
"copyToClipboard": {
"message": "Panoya kopyala"
},
"copyButton": {
"message": " Kopyala "
},
"copyPrivateKey": {
"message": "Bu sizin özel anahtarınız (kopyalamak için tıklayın)"
},
"create": {
"message": "Yarat"
},
"createAccount": {
"message": "Hesap Oluştur"
},
"createDen": {
"message": "Yarat"
},
"crypto": {
"message": "Kripto",
"description": "Kambiyo tipi (kripto para)"
},
"currentConversion": {
"message": "Geçerli çevirme"
},
"currentNetwork": {
"message": "Geçerli Ağ"
},
"customGas": {
"message": "Gas'i özelleştir"
},
"customToken": {
"message": "Özel Jeton"
},
"customize": {
"message": "Özelleştir"
},
"customRPC": {
"message": "Özel RPC"
},
"decimalsMustZerotoTen": {
"message": "Ondalıklar en azından 0 olmalı ve 36'dan büyük olmamalı."
},
"decimal": {
"message": "Ondalık hassasiyeti"
},
"defaultNetwork": {
"message": "Ether işlemleri için varsayılan ağ Main Net."
},
"denExplainer": {
"message": "DEN'iniz MetaMask içersinde parola-şifrelenmiş deponuzdur."
},
"deposit": {
"message": "Yatır"
},
"depositBTC": {
"message": "BTC'inizi aşağıdaki adrese yatırın:"
},
"depositCoin": {
"message": "$1'nızı aşağıdaki adrese yatırın",
"description": "Kullanıcıya hangi jetonu seçtiyse onu yatırmasını shapeshift ile söyler."
},
"depositEth": {
"message": "Eth yatır"
},
"depositEther": {
"message": "Ether yatır"
},
"depositFiat": {
"message": "Para yatır"
},
"depositFromAccount": {
"message": "Başka bir hesaptan yatır"
},
"depositShapeShift": {
"message": "ShapeShift ile yatır"
},
"depositShapeShiftExplainer": {
"message": "Eğer başka kripto paralara sahipseniz, MetaMask cüzdanınıza direk olarak Ether yatırabilirsiniz. Hesaba gerek yoktur."
},
"details": {
"message": "Ayrıntılar"
},
"directDeposit": {
"message": "Direk Yatırma"
},
"directDepositEther": {
"message": "Direk Ether Yatırma"
},
"directDepositEtherExplainer": {
"message": "Eğer çoktan Etheriniz varsa, yeni hesabınıza Ether aktarmanın en kolay yolu direk yatırmadır."
},
"done": {
"message": "Bitti"
},
"downloadStateLogs": {
"message": "Durum kayıtlarını indir"
},
"dropped": {
"message": "Bırakıldı"
},
"edit": {
"message": "Düzenle"
},
"editAccountName": {
"message": "Hesap ismini düzenle"
},
"emailUs": {
"message": "Bize e-posta atın!"
},
"encryptNewDen": {
"message": "Yeni DEN'inizi şifreleyin"
},
"enterPassword": {
"message": "Parolanızı girin"
},
"enterPasswordConfirm": {
"message": "Onaylamak için parolanızı girin"
},
"passwordNotLongEnough": {
"message": "Parola yeterince uzun değil"
},
"passwordsDontMatch": {
"message": "Parolalar eşleşmiyor"
},
"etherscanView": {
"message": "Hesabı Etherscan üzerinde izle"
},
"exchangeRate": {
"message": "Döviz kuru"
},
"exportPrivateKey": {
"message": "Özel anahtarı ver"
},
"exportPrivateKeyWarning": {
"message": "Özel anahtarınızı vermek sizin sorumluluğunuzdadır."
},
"failed": {
"message": "Başarısız oldu"
},
"fiat": {
"message": "Para",
"description": "Döviz türü"
},
"fileImportFail": {
"message": "Dosya alma çalışmıyor mu? Buraya tıklayın!",
"description": "Kullanıcıların hesaplarını JSON dosyasından almalarına yardım eder"
},
"followTwitter": {
"message": "Bizi twitter'da takip edin"
},
"from": {
"message": "Kimden"
},
"fromToSame": {
"message": "Kimden ve kime adresi aynı olamaz"
},
"fromShapeShift": {
"message": "ShapeShift'den"
},
"gas": {
"message": "Gas",
"description": "Gas maliyetinin kısa indikatörü"
},
"gasFee": {
"message": "Gas Ücreti"
},
"gasLimit": {
"message": "Gas Limiti"
},
"gasLimitCalculation": {
"message": "Önerilen gas limitini ağ başarı oranını baz alarak hesaplıyoruz."
},
"gasLimitRequired": {
"message": "Gas limiti gereklidir"
},
"gasLimitTooLow": {
"message": "Gas limiti en az 21000 olmalıdır"
},
"generatingSeed": {
"message": "Kaynak Oluşturuyor..."
},
"gasPrice": {
"message": "Gas Fiyatı (GWEI)"
},
"gasPriceCalculation": {
"message": "Önerilen gas fiyatını ağ başarı oranını baz alarak hesaplıyoruz."
},
"gasPriceRequired": {
"message": "Gas Fiyatı Gereklidir"
},
"getEther": {
"message": "Ether Al"
},
"getEtherFromFaucet": {
"message": "Musluktan $1 karşılığı Ether alın",
"description": "Ether musluğunun ağ ismini gösterir"
},
"greaterThanMin": {
"message": "must be greater than or equal to $1.",
"description": "helper for inputting hex as decimal input"
},
"here": {
"message": "burada",
"description": "daha fazla bilgi için -buraya tıklayın- (troubleTokenBalances ile gidiyor)"
},
"hereList": {
"message": "İşte bir liste!!!!"
},
"hide": {
"message": "Gizle"
},
"hideToken": {
"message": "Jetonu gizle"
},
"hideTokenPrompt": {
"message": "Jetonu gizle?"
},
"howToDeposit": {
"message": "Ether'i nasıl yatırmak istersiniz?"
},
"holdEther": {
"message": "Ether ve jeton tutmanızı sağlar ve merkezi olmayan uygulamalar ve sizin aranızda köprü vazifesi görür."
},
"import": {
"message": "Al",
"description": "Seçilen dosyadan hesap alma düğmesi. "
},
"importAccount": {
"message": "Hesap Al"
},
"importAccountMsg": {
"message":" Alınan hesaplar orjinal kaynakifadenizle yarattığınız MetaMask hesabınızla ilişkilendirilmez. Alınan hesaplar ile ilgili daha fazla bilgi edinin "
},
"importAnAccount": {
"message": "Hesap al"
},
"importDen": {
"message": "Varolan DEN al"
},
"imported": {
"message": "Alındı",
"description": "Hesabın keyringe başarı ile alındığını gösteren durum"
},
"infoHelp": {
"message": "Bilgi ve yardım"
},
"insufficientFunds": {
"message": "Yetersiz kaynak."
},
"insufficientTokens": {
"message": "Yetersiz Jeton."
},
"invalidAddress": {
"message": "Geçersiz adres"
},
"invalidAddressRecipient": {
"message": "Alıcı adresi geçersiz"
},
"invalidGasParams": {
"message": "Geçersiz gas parametreleri"
},
"invalidInput": {
"message": "Geçersiz giriş."
},
"invalidRequest": {
"message": "Geçersiz istek"
},
"invalidRPC": {
"message": "Geçersiz RPC URI"
},
"jsonFail": {
"message": "Birşeyler yanlış gitti. JSON dosyanızın düzgün derlendiğinden emin olun."
},
"jsonFile": {
"message": "JSON Dosyası",
"description": "Hesap alımı için düzenle"
},
"keepTrackTokens": {
"message": "MetaMask hesabınızla satın aldığınız jetonların kaydını tutun."
},
"kovan": {
"message": "Kovan Test Ağı"
},
"knowledgeDataBase": {
"message": "Bilgi veritabanımızı ziyaret edin"
},
"max": {
"message": "Maksimum"
},
"learnMore": {
"message": "Daha fazla bilgi."
},
"lessThanMax": {
"message": "$1'den az veya eşit olmalıdır.",
"description": "Onaltılık sayıyı ondalık olarak girmek için yardımcı"
},
"likeToAddTokens": {
"message": "Bu jetonlara adres eklemek ister misiniz?"
},
"links": {
"message": "Bağlantılar"
},
"limit": {
"message": "Limit"
},
"loading": {
"message": "Yükleniyor..."
},
"loadingTokens": {
"message": "Jetonlar yükleniyor..."
},
"localhost": {
"message": "Localhost 8545"
},
"login": {
"message": "Giriş yap"
},
"logout": {
"message": ıkış"
},
"loose": {
"message": "Gevşek"
},
"loweCaseWords": {
"message": "kaynak kelimeleri sadece küçük harflerden oluşabilir."
},
"mainnet": {
"message": "Main Ethereum Ağı"
},
"message": {
"message": "Mesaj"
},
"metamaskDescription": {
"message": "MetaMask Ethereum için güvenli bir kimlik kasasıdır."
},
"min": {
"message": "Minimum"
},
"myAccounts": {
"message": "Hesaplarım"
},
"mustSelectOne": {
"message": "En az bir jeton seçilmeli"
},
"needEtherInWallet": {
"message": "MetaMask kullanarak merkezi olamayan uygulamalarla etkileşmek için cüzdanınızda Ether bulunmalıdır."
},
"needImportFile": {
"message": "Almak için bir dosya seçmelisiniz.",
"description": "Kullanıcı bir hesap alır ve devam etmek içinbir dosya seçmelidir."
},
"needImportPassword": {
"message": "Seçilen dosya için bir parola girmelisiniz.",
"description": "Hesap almak için parola ve dosya gerekiyor."
},
"negativeETH": {
"message": "Negatif ETH miktarları gönderilemez."
},
"networks": {
"message": "Ağlar"
},
"newAccount": {
"message": "Yeni Hesap"
},
"newAccountNumberName": {
"message": "Hesap $1",
"description": "Hesap yaratma ekranındaki bir sonraki hesabın varsayılan ismi"
},
"newContract": {
"message": "Yeni Sözleşme"
},
"newPassword": {
"message": "Yeni Parola (min 8 karakter)"
},
"newRecipient": {
"message": "Yeni alıcı"
},
"newRPC": {
"message": "Yeni RPC URL"
},
"next": {
"message": "Sonraki"
},
"noAddressForName": {
"message": "Bu isim için bir adres tanımlanmamış."
},
"noDeposits": {
"message": "Yatırma alınmadı"
},
"noTransactionHistory": {
"message": "İşlem geçmişi yok."
},
"noTransactions": {
"message": "İşlem yok"
},
"notStarted": {
"message": "Başlamadı"
},
"oldUI": {
"message": "Eski UI"
},
"oldUIMessage": {
"message": "Eski UI'a döndünüz. Yeni UI'a üst sağ sekme menüsündeki seçenek ile dönebilirsiniz."
},
"or": {
"message": "veya",
"description": "Yeni bir hesap yaratmak veya almak arasındaki seçim"
},
"passwordCorrect": {
"message": "Lütfen parolanın doğru olduğuna emin olun."
},
"passwordMismatch": {
"message": "parolalar eşleşmiyor",
"description": "parola yaratma işleminde, iki yeni parola alanı eşleşmiyor."
},
"passwordShort": {
"message": "parola yeterince uzun değil",
"description": "parola yaratma işleminde, parola güvenli olacak kadar uzun değil."
},
"pastePrivateKey": {
"message": "Özel anahtar dizinizi buraya yapıştırın:",
"description": "Özel anahtardan hesap almak için"
},
"pasteSeed": {
"message": "Kaynak ifadenizi buraya yapıştırın!"
},
"personalAddressDetected": {
"message": "Kişisel adres tespit edilidi. Jeton sözleşme adresini girin."
},
"pleaseReviewTransaction": {
"message": "Lütfen işleminizi gözden geçirin."
},
"popularTokens": {
"message": "Popüler Jetonlar"
},
"privacyMsg": {
"message": "Gizlilik Şartları"
},
"privateKey": {
"message": "Özel Anahtar",
"description": "Hesap alırken bu tip bir dosya seçin"
},
"privateKeyWarning": {
"message": "Uyarı: Bu anahtarı kimse ile paylaşmayın. Özel anahtarlarınıza sahip herkes hesaplarınızıdaki tüm varlığınızı çalabilir."
},
"privateNetwork": {
"message": "Özel Ağ"
},
"qrCode": {
"message": "QR Kodunu göster"
},
"readdToken": {
"message": "Gelecekte Bu jetonu hesap seçenekleri menüsünde “Jeton ekle”'ye giderek geri ekleyebilirsiniz."
},
"readMore": {
"message": "Burada daha fazla okuyun."
},
"readMore2": {
"message": "Daha fazla okuyun."
},
"receive": {
"message": "Al"
},
"recipientAddress": {
"message": "Alıcı adresi"
},
"refundAddress": {
"message": "İade adresiniz"
},
"rejected": {
"message": "Rededildi"
},
"resetAccount": {
"message": "Hesabı sıfıla"
},
"restoreFromSeed": {
"message": "Kaynak ifadeden geri getir. Restore from seed phrase"
},
"restoreVault": {
"message": "Kasayı geri getir"
},
"required": {
"message": "Gerekli"
},
"retryWithMoreGas": {
"message": "Daha yüksek bir gas fiyatı ile tekrar dene"
},
"walletSeed": {
"message": "Cüzdan Kaynağı"
},
"revealSeedWords": {
"message": "Kaynak kelimelerini göster"
},
"revealSeedWordsWarning": {
"message": "Açık bir yerde kaynak kelimeliriniz geri getirmeyin! Bu kelimeler tüm hesaplarınızı çalmak için kullanılabilir."
},
"revert": {
"message": "Geri döndür"
},
"rinkeby": {
"message": "Rinkeby Test Ağı"
},
"ropsten": {
"message": "Ropsten Test Ağı"
},
"currentRpc": {
"message": "Geçerli RPC"
},
"connectingToMainnet": {
"message": "Main Ethereum Ağına bağlanıyor"
},
"connectingToRopsten": {
"message": "Ropsten Test Ağına bağlanıyor"
},
"connectingToKovan": {
"message": "Kovan Test Ağına bağlanıyor"
},
"connectingToRinkeby": {
"message": "Rinkeby Test Ağına bağlanıyor"
},
"connectingToUnknown": {
"message": "Bilinmeyen Ağa bağlanıyor"
},
"sampleAccountName": {
"message": "E.g. Yeni hesabım",
"description": "Kullanıcının hesabına okunabilir isim ekleme konseptini anlamasına yardımcı olmak."
},
"save": {
"message": "Kaydet"
},
"reprice_title": {
"message": "İşlemi Yeniden Fiyatlandır"
},
"reprice_subtitle": {
"message": "İşlemizi hızlandırmayı denemek için gas fiyatınızı yükseltin."
},
"saveAsFile": {
"message": "Dosya olarak kaydet",
"description": "Hesap verme işlemi"
},
"saveSeedAsFile": {
"message": "Kaynak Kelimelerini Dosya olarak Kaydet"
},
"search": {
"message": "Ara"
},
"secretPhrase": {
"message": "Kasanızı geri getirmek için gizli 12 kelimelik ifadenizi giriniz."
},
"newPassword8Chars": {
"message": "Yeni Parola (minumum 8 karakter)"
},
"seedPhraseReq": {
"message": "Kaynak ifadeleri 12 kelimedir."
},
"select": {
"message": "Seç"
},
"selectCurrency": {
"message": "Döviz Seç"
},
"selectService": {
"message": "Servis Seç"
},
"selectType": {
"message": "Tip Seç"
},
"send": {
"message": "Gönder"
},
"sendETH": {
"message": "ETH Gönder"
},
"sendTokens": {
"message": "Jeton Gönder"
},
"onlySendToEtherAddress": {
"message": "Ethereum adresine sadece ETH gönder."
},
"searchTokens": {
"message": "Jeton ara"
},
"sendTokensAnywhere": {
"message": "Ethereum hesabı olan birine Jeton gönder"
},
"settings": {
"message": "Ayarlar"
},
"info": {
"message": "Bilgi"
},
"shapeshiftBuy": {
"message": "Shapeshift ile satın al"
},
"showPrivateKeys": {
"message": "Özel anahtarları göster"
},
"showQRCode": {
"message": "QR Kodu göster"
},
"sign": {
"message": "İmza"
},
"signed": {
"message": "İmzalandı"
},
"signMessage": {
"message": "Mesajı İmzala"
},
"signNotice": {
"message": "Bu mesajı imzalamanın tehlikeli \nyan etkileri olabilir. Tamamen güvendiğiniz sitelerden \ngelen mesajları hesabınızla imzalayınız.\n Bu tehlikeli metod gelecek versiyonlarda çıkarılacaktır. "
},
"sigRequest": {
"message": "İmza isteği"
},
"sigRequested": {
"message": "İmza isteniyor"
},
"spaceBetween": {
"message": "Kelimeler arası sadece bir boşluk olabilir."
},
"status": {
"message": "Durum"
},
"stateLogs": {
"message": "Durum Kayıtları"
},
"stateLogsDescription": {
"message": "Durum kayıtlarıık hesap adresinizi ve gönderilen işlemleri içerir."
},
"stateLogError": {
"message": "Durum kayıtlarını alma hatası"
},
"submit": {
"message": "Gönder"
},
"submitted": {
"message": "Gönderildi"
},
"supportCenter": {
"message": "Destek merkezimizi ziyaret edin"
},
"symbolBetweenZeroTen": {
"message": "Sembol 0 ve 10 karakter aralığında olmalıdır."
},
"takesTooLong": {
"message": "Çok mu uzun sürüyor?"
},
"terms": {
"message": "Kullanım şartları"
},
"testFaucet": {
"message": "Test Musluğu"
},
"to": {
"message": "Kime: "
},
"toETHviaShapeShift": {
"message": "ShapeShift üstünden $1'dan ETH'e",
"description": "system will fill in deposit type in start of message"
},
"tokenAddress": {
"message": "Jeton Adresi"
},
"tokenAlreadyAdded": {
"message": "Jeton çoktan eklenmiş."
},
"tokenBalance": {
"message": "Jeton bakiyeniz:"
},
"tokenSelection": {
"message": "Jeton arayın veya popüler jeton listemizden seçin."
},
"tokenSymbol": {
"message": "Jeton Sembolü"
},
"tokenWarning1": {
"message": "MetaMask hesabınızla aldığınız jetonların kaydını tutun. Başka bir hesapla jetonlar satın aldıysanız, o jetonlar burada gözükmeyecektir."
},
"total": {
"message": "Toplam"
},
"transactions": {
"message": "işlemler"
},
"transactionError": {
"message": "İşlem Hatası. Sözleşme kodundan kural dışı durum fırlatıldı."
},
"transactionMemo": {
"message": "İşlem notu (opsiyonel)"
},
"transactionNumber": {
"message": "İşlem numarası"
},
"transfers": {
"message": "Transferler"
},
"troubleTokenBalances": {
"message": "Jeton bakiyelerinizi yüklerken sorun yaşadık. Buradan izleyebilirsiniz ",
"description": "Jeton bakiyelerini görmek için bir link (burası) ile takip ediliyor"
},
"twelveWords": {
"message": "MetaMask hesaplarınızı geri getirmenin tek yolu bu 12 kelimedir.\nBu kelimeleri güvenli ve gizli bir yerde saklayın."
},
"typePassword": {
"message": "Parolanızı girin"
},
"uiWelcome": {
"message": "Yeni UI (Beta)'ya hoşgeldiniz"
},
"uiWelcomeMessage": {
"message": "Şu anda yeni Metamask UI kullanmaktasınız. Gözatın, jeton gönderme gibi yeni özellikleri deneyin ve herhangi bir sorunlar karşılaşırsanız bize haber verin"
},
"unapproved": {
"message": "Onaylanmadı"
},
"unavailable": {
"message": "Mevcut değil"
},
"unknown": {
"message": "Bilinmeyen"
},
"unknownNetwork": {
"message": "Bilinmeyen özel ağ"
},
"unknownNetworkId": {
"message": "Bilinmeyen ağ IDsi"
},
"uriErrorMsg": {
"message": "URIler için HTTP/HTTPS öneki gerekmektedir."
},
"usaOnly": {
"message": "Sadece ABD",
"description": "Bu dövizi sadece ABD ikamet edenler kullanabilir"
},
"usedByClients": {
"message": "Farklı istemciler tarafından kullanılmakta"
},
"useOldUI": {
"message": "Eski UI kullan"
},
"validFileImport": {
"message": "Almak için geçerli bir dosya seçmelisiniz"
},
"vaultCreated": {
"message": "Kasa Yaratıldı"
},
"viewAccount": {
"message": "Hesabı İncele"
},
"visitWebSite": {
"message": "Web sitemizi ziyaret edin"
},
"warning": {
"message": "Uyarı"
},
"welcomeBeta": {
"message": "MetaMask Beta'ya Hoşgeldiniz"
},
"whatsThis": {
"message": "Bu nedir?"
},
"yourSigRequested": {
"message": "İmzanız isteniyor"
},
"youSign": {
"message": "İmzalıyorsunuz"
}
}

View File

@ -1,7 +1,7 @@
{
"name": "__MSG_appName__",
"short_name": "__MSG_appName__",
"version": "4.5.0",
"version": "4.5.5",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "__MSG_appDescription__",

View File

@ -20,6 +20,7 @@ const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry')
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 STORAGE_KEY = 'metamask-config'
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
@ -77,6 +78,38 @@ async function loadStateFromPersistence () {
diskStore.getState() ||
migrator.generateInitialState(firstTimeState)
// check if somehow state is empty
// this should never happen but new error reporting suggests that it has
// for a small number of users
// https://github.com/metamask/metamask-extension/issues/3919
if (versionedData && !versionedData.data) {
// try to recover from diskStore incase only localStore is bad
const diskStoreState = diskStore.getState()
if (diskStoreState && diskStoreState.data) {
// we were able to recover (though it might be old)
versionedData = diskStoreState
const vaultStructure = getObjStructure(versionedData)
raven.captureMessage('MetaMask - Empty vault found - recovered from diskStore', {
// "extra" key is required by Sentry
extra: { vaultStructure },
})
} else {
// unable to recover, clear state
versionedData = migrator.generateInitialState(firstTimeState)
raven.captureMessage('MetaMask - Empty vault found - unable to recover')
}
}
// report migration errors to sentry
migrator.on('error', (err) => {
// get vault structure without secrets
const vaultStructure = getObjStructure(versionedData)
raven.captureException(err, {
// "extra" key is required by Sentry
extra: { vaultStructure },
})
})
// migrate data
versionedData = await migrator.migrateData(versionedData)
if (!versionedData) {
@ -84,7 +117,14 @@ async function loadStateFromPersistence () {
}
// write to disk
if (localStore.isSupported) localStore.set(versionedData)
if (localStore.isSupported) {
localStore.set(versionedData)
} else {
// throw in setTimeout so as to not block boot
setTimeout(() => {
throw new Error('MetaMask - Localstore not supported')
})
}
// return just the data
return versionedData.data
@ -122,9 +162,9 @@ function setupController (initState, initLangCode) {
asStream(controller.store),
debounce(1000),
storeTransform(versionifyData),
storeTransform(syncDataWithExtension),
storeTransform(persistData),
(error) => {
log.error('pump hit error', error)
log.error('MetaMask - Persistence pipeline failed', error)
}
)
@ -133,7 +173,13 @@ function setupController (initState, initLangCode) {
return versionedData
}
function syncDataWithExtension(state) {
function persistData(state) {
if (!state) {
throw new Error('MetaMask - updated state is missing', state)
}
if (!state.data) {
throw new Error('MetaMask - updated state does not have data', state)
}
if (localStore.isSupported) {
localStore.set(state)
.catch((err) => {

View File

@ -131,7 +131,11 @@ function documentElementCheck () {
}
function blacklistedDomainCheck () {
var blacklistedDomains = ['uscourts.gov', 'dropbox.com']
var blacklistedDomains = [
'uscourts.gov',
'dropbox.com',
'webbyawards.com',
]
var currentUrl = window.location.href
var currentRegex
for (let i = 0; i < blacklistedDomains.length; i++) {

View File

@ -185,9 +185,10 @@ module.exports = class TransactionController extends EventEmitter {
async addUnapprovedTransaction (txParams) {
// validate
await this.txGasUtil.validateTxParams(txParams)
const normalizedTxParams = this._normalizeTxParams(txParams)
this._validateTxParams(normalizedTxParams)
// construct txMeta
let txMeta = this.txStateManager.generateTxMeta({txParams})
let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams })
this.addTx(txMeta)
this.emit('newUnapprovedTx', txMeta)
// add default tx params
@ -215,7 +216,6 @@ module.exports = class TransactionController extends EventEmitter {
}
txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16))
txParams.value = txParams.value || '0x0'
if (txParams.to === null) delete txParams.to
// set gasLimit
return await this.txGasUtil.analyzeGasUsage(txMeta)
}
@ -314,6 +314,60 @@ module.exports = class TransactionController extends EventEmitter {
// PRIVATE METHODS
//
_normalizeTxParams (txParams) {
// functions that handle normalizing of that key in txParams
const whiteList = {
from: from => ethUtil.addHexPrefix(from).toLowerCase(),
to: to => ethUtil.addHexPrefix(txParams.to).toLowerCase(),
nonce: nonce => ethUtil.addHexPrefix(nonce),
value: value => ethUtil.addHexPrefix(value),
data: data => ethUtil.addHexPrefix(data),
gas: gas => ethUtil.addHexPrefix(gas),
gasPrice: gasPrice => ethUtil.addHexPrefix(gasPrice),
}
// apply only keys in the whiteList
const normalizedTxParams = {}
Object.keys(whiteList).forEach((key) => {
if (txParams[key]) normalizedTxParams[key] = whiteList[key](txParams[key])
})
return normalizedTxParams
}
_validateTxParams (txParams) {
this._validateFrom(txParams)
this._validateRecipient(txParams)
if ('value' in txParams) {
const value = txParams.value.toString()
if (value.includes('-')) {
throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
}
if (value.includes('.')) {
throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`)
}
}
}
_validateFrom (txParams) {
if ( !(typeof txParams.from === 'string') ) throw new Error(`Invalid from address ${txParams.from} not a string`)
if (!ethUtil.isValidAddress(txParams.from)) throw new Error('Invalid from address')
}
_validateRecipient (txParams) {
if (txParams.to === '0x' || txParams.to === null ) {
if (txParams.data) {
delete txParams.to
} else {
throw new Error('Invalid recipient address')
}
} else if ( txParams.to !== undefined && !ethUtil.isValidAddress(txParams.to) ) {
throw new Error('Invalid recipient address')
}
return txParams
}
_markNonceDuplicatesDropped (txId) {
this.txStateManager.setTxStatusConfirmed(txId)
// get the confirmed transactions nonce and from address

View File

@ -2,14 +2,16 @@ const extension = require('extensionizer')
const promisify = require('pify')
const allLocales = require('../../_locales/index.json')
const existingLocaleCodes = allLocales.map(locale => locale.code)
const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().replace('_', '-'))
async function getFirstPreferredLangCode () {
const userPreferredLocaleCodes = await promisify(
extension.i18n.getAcceptLanguages,
{ errorFirst: false }
)()
const firstPreferredLangCode = userPreferredLocaleCodes.find(code => existingLocaleCodes.includes(code))
const firstPreferredLangCode = userPreferredLocaleCodes
.map(code => code.toLowerCase())
.find(code => existingLocaleCodes.includes(code))
return firstPreferredLangCode || 'en'
}

View File

@ -0,0 +1,33 @@
const clone = require('clone')
module.exports = getObjStructure
// This will create an object that represents the structure of the given object
// it replaces all values with the result of their type
// {
// "data": {
// "CurrencyController": {
// "conversionDate": "number",
// "conversionRate": "number",
// "currentCurrency": "string"
// }
// }
function getObjStructure(obj) {
const structure = clone(obj)
return deepMap(structure, (value) => {
return value === null ? 'null' : typeof value
})
}
function deepMap(target = {}, visit) {
Object.entries(target).forEach(([key, value]) => {
if (typeof value === 'object' && value !== null) {
target[key] = deepMap(value, visit)
} else {
target[key] = visit(value)
}
})
return target
}

View File

@ -3,7 +3,8 @@ module.exports = function isPopupOrNotification () {
// 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(?:\?.+)*$/)) {
if (url.match(/popup.html(?:\?.+)*$/) ||
url.match(/home.html(?:\?.+)*$/) || url.match(/home.html(?:#.*)*$/)) {
return 'popup'
} else {
return 'notification'

View File

@ -1,6 +1,9 @@
class Migrator {
const EventEmitter = require('events')
class Migrator extends EventEmitter {
constructor (opts = {}) {
super()
const migrations = opts.migrations || []
// sort migrations by version
this.migrations = migrations.sort((a, b) => a.version - b.version)
@ -12,13 +15,29 @@ class Migrator {
// run all pending migrations on meta in place
async migrateData (versionedData = this.generateInitialState()) {
// get all migrations that have not yet been run
const pendingMigrations = this.migrations.filter(migrationIsPending)
// perform each migration
for (const index in pendingMigrations) {
const migration = pendingMigrations[index]
versionedData = await migration.migrate(versionedData)
if (!versionedData.data) throw new Error('Migrator - migration returned empty data')
if (versionedData.version !== undefined && versionedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly')
try {
// attempt migration and validate
const migratedData = await migration.migrate(versionedData)
if (!migratedData.data) throw new Error('Migrator - migration returned empty data')
if (migratedData.version !== undefined && migratedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly')
// accept the migration as good
versionedData = migratedData
} catch (err) {
// rewrite error message to add context without clobbering stack
const originalErrorMessage = err.message
err.message = `MetaMask Migration Error #${migration.version}: ${originalErrorMessage}`
console.warn(err.stack)
// emit error instead of throw so as to not break the run (gracefully fail)
this.emit('error', err)
// stop migrating and use state as is
return versionedData
}
}
return versionedData

View File

@ -4,7 +4,7 @@ const {
BnMultiplyByFraction,
bnToHex,
} = require('./util')
const { addHexPrefix, isValidAddress } = require('ethereumjs-util')
const { addHexPrefix } = require('ethereumjs-util')
const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
/*
@ -100,37 +100,4 @@ module.exports = class TxGasUtil {
// otherwise use blockGasLimit
return bnToHex(upperGasLimitBn)
}
async validateTxParams (txParams) {
this.validateFrom(txParams)
this.validateRecipient(txParams)
if ('value' in txParams) {
const value = txParams.value.toString()
if (value.includes('-')) {
throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
}
if (value.includes('.')) {
throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`)
}
}
}
validateFrom (txParams) {
if ( !(typeof txParams.from === 'string') ) throw new Error(`Invalid from address ${txParams.from} not a string`)
if (!isValidAddress(txParams.from)) throw new Error('Invalid from address')
}
validateRecipient (txParams) {
if (txParams.to === '0x' || txParams.to === null ) {
if (txParams.data) {
delete txParams.to
} else {
throw new Error('Invalid recipient address')
}
} else if ( txParams.to !== undefined && !isValidAddress(txParams.to) ) {
throw new Error('Invalid recipient address')
}
return txParams
}
}

View File

@ -92,8 +92,10 @@ module.exports = class TransactionStateManager extends EventEmitter {
// or rejected tx's.
// not tx's that are pending or unapproved
if (txCount > txHistoryLimit - 1) {
const index = transactions.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected')
transactions.splice(index, 1)
let index = transactions.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected')
if (index !== -1) {
transactions.splice(index, 1)
}
}
transactions.push(txMeta)
this._saveTxList(transactions)
@ -108,6 +110,10 @@ module.exports = class TransactionStateManager extends EventEmitter {
updateTx (txMeta, note) {
// validate txParams
if (txMeta.txParams) {
if (typeof txMeta.txParams.data === 'undefined') {
delete txMeta.txParams.data
}
this.validateTxParams(txMeta.txParams)
}
@ -140,8 +146,16 @@ module.exports = class TransactionStateManager extends EventEmitter {
validateTxParams(txParams) {
Object.keys(txParams).forEach((key) => {
const value = txParams[key]
if (typeof value !== 'string') throw new Error(`${key}: ${value} in txParams is not a string`)
if (!ethUtil.isHexPrefixed(value)) throw new Error('is not hex prefixed, everything on txParams must be hex prefixed')
// validate types
switch (key) {
case 'chainId':
if (typeof value !== 'number' && typeof value !== 'string') throw new Error(`${key} in txParams is not a Number or hex string. got: (${value})`)
break
default:
if (typeof value !== 'string') throw new Error(`${key} in txParams is not a string. got: (${value})`)
if (!ethUtil.isHexPrefixed(value)) throw new Error(`${key} in txParams is not hex prefixed. got: (${value})`)
break
}
})
}

View File

@ -27,8 +27,11 @@ module.exports = {
function transformState (state) {
const newState = state
if (newState.config.provider.type === 'testnet') {
newState.config.provider.type = 'ropsten'
const { config } = newState
if ( config && config.provider ) {
if (config.provider.type === 'testnet') {
newState.config.provider.type = 'ropsten'
}
}
return newState
}

View File

@ -28,11 +28,14 @@ module.exports = {
function transformState (state) {
const newState = state
const transactions = newState.TransactionController.transactions
newState.TransactionController.transactions = transactions.map((txMeta) => {
if (!txMeta.err) return txMeta
else if (txMeta.err.message === 'Gave up submitting tx.') txMeta.status = 'failed'
return txMeta
})
const { TransactionController } = newState
if (TransactionController && TransactionController.transactions) {
const transactions = TransactionController.transactions
newState.TransactionController.transactions = transactions.map((txMeta) => {
if (!txMeta.err) return txMeta
else if (txMeta.err.message === 'Gave up submitting tx.') txMeta.status = 'failed'
return txMeta
})
}
return newState
}

View File

@ -28,14 +28,18 @@ module.exports = {
function transformState (state) {
const newState = state
const transactions = newState.TransactionController.transactions
newState.TransactionController.transactions = transactions.map((txMeta) => {
if (!txMeta.err) return txMeta
if (txMeta.err === 'transaction with the same hash was already imported.') {
txMeta.status = 'submitted'
delete txMeta.err
}
return txMeta
})
const { TransactionController } = newState
if (TransactionController && TransactionController.transactions) {
const transactions = newState.TransactionController.transactions
newState.TransactionController.transactions = transactions.map((txMeta) => {
if (!txMeta.err) return txMeta
if (txMeta.err === 'transaction with the same hash was already imported.') {
txMeta.status = 'submitted'
delete txMeta.err
}
return txMeta
})
}
return newState
}

View File

@ -27,14 +27,17 @@ module.exports = {
function transformState (state) {
const newState = state
const transactions = newState.TransactionController.transactions
newState.TransactionController.transactions = transactions.map((txMeta) => {
if (!txMeta.status === 'failed') return txMeta
if (txMeta.retryCount > 0 && txMeta.retryCount < 2) {
txMeta.status = 'submitted'
delete txMeta.err
}
return txMeta
})
const { TransactionController } = newState
if (TransactionController && TransactionController.transactions) {
const transactions = newState.TransactionController.transactions
newState.TransactionController.transactions = transactions.map((txMeta) => {
if (!txMeta.status === 'failed') return txMeta
if (txMeta.retryCount > 0 && txMeta.retryCount < 2) {
txMeta.status = 'submitted'
delete txMeta.err
}
return txMeta
})
}
return newState
}

View File

@ -29,24 +29,27 @@ module.exports = {
function transformState (state) {
const newState = state
const transactions = newState.TransactionController.transactions
newState.TransactionController.transactions = transactions.map((txMeta) => {
// no history: initialize
if (!txMeta.history || txMeta.history.length === 0) {
const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
txMeta.history = [snapshot]
const { TransactionController } = newState
if (TransactionController && TransactionController.transactions) {
const transactions = newState.TransactionController.transactions
newState.TransactionController.transactions = transactions.map((txMeta) => {
// no history: initialize
if (!txMeta.history || txMeta.history.length === 0) {
const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
txMeta.history = [snapshot]
return txMeta
}
// has history: migrate
const newHistory = (
txStateHistoryHelper.migrateFromSnapshotsToDiffs(txMeta.history)
// remove empty diffs
.filter((entry) => {
return !Array.isArray(entry) || entry.length > 0
})
)
txMeta.history = newHistory
return txMeta
}
// has history: migrate
const newHistory = (
txStateHistoryHelper.migrateFromSnapshotsToDiffs(txMeta.history)
// remove empty diffs
.filter((entry) => {
return !Array.isArray(entry) || entry.length > 0
})
)
txMeta.history = newHistory
return txMeta
})
})
}
return newState
}

View File

@ -29,32 +29,36 @@ module.exports = {
function transformState (state) {
const newState = state
const transactions = newState.TransactionController.transactions
const { TransactionController } = newState
if (TransactionController && TransactionController.transactions) {
newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => {
if (txMeta.status !== 'submitted') return txMeta
const transactions = newState.TransactionController.transactions
const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed')
.filter((tx) => tx.txParams.from === txMeta.txParams.from)
.filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
const highestConfirmedNonce = getHighestNonce(confirmedTxs)
newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => {
if (txMeta.status !== 'submitted') return txMeta
const pendingTxs = txList.filter((tx) => tx.status === 'submitted')
.filter((tx) => tx.txParams.from === txMeta.txParams.from)
.filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
const highestContinuousNonce = getHighestContinuousFrom(pendingTxs, highestConfirmedNonce)
const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed')
.filter((tx) => tx.txParams.from === txMeta.txParams.from)
.filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
const highestConfirmedNonce = getHighestNonce(confirmedTxs)
const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce)
const pendingTxs = txList.filter((tx) => tx.status === 'submitted')
.filter((tx) => tx.txParams.from === txMeta.txParams.from)
.filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
const highestContinuousNonce = getHighestContinuousFrom(pendingTxs, highestConfirmedNonce)
if (parseInt(txMeta.txParams.nonce, 16) > maxNonce + 1) {
txMeta.status = 'failed'
txMeta.err = {
message: 'nonce too high',
note: 'migration 019 custom error',
const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce)
if (parseInt(txMeta.txParams.nonce, 16) > maxNonce + 1) {
txMeta.status = 'failed'
txMeta.err = {
message: 'nonce too high',
note: 'migration 019 custom error',
}
}
}
return txMeta
})
return txMeta
})
}
return newState
}

View File

@ -28,12 +28,15 @@ module.exports = {
function transformState (state) {
const newState = state
const transactions = newState.TransactionController.transactions
const { TransactionController } = newState
if (TransactionController && TransactionController.transactions) {
const transactions = newState.TransactionController.transactions
newState.TransactionController.transactions = transactions.map((txMeta) => {
if (txMeta.status !== 'submitted' || txMeta.submittedTime) return txMeta
txMeta.submittedTime = (new Date()).getTime()
return txMeta
})
newState.TransactionController.transactions = transactions.map((txMeta) => {
if (txMeta.status !== 'submitted' || txMeta.submittedTime) return txMeta
txMeta.submittedTime = (new Date()).getTime()
return txMeta
})
}
return newState
}

View File

@ -28,23 +28,27 @@ module.exports = {
function transformState (state) {
const newState = state
const transactions = newState.TransactionController.transactions
if (transactions.length <= 40) return newState
const { TransactionController } = newState
if (TransactionController && TransactionController.transactions) {
const transactions = newState.TransactionController.transactions
let reverseTxList = transactions.reverse()
let stripping = true
while (reverseTxList.length > 40 && stripping) {
let txIndex = reverseTxList.findIndex((txMeta) => {
return (txMeta.status === 'failed' ||
txMeta.status === 'rejected' ||
txMeta.status === 'confirmed' ||
txMeta.status === 'dropped')
})
if (txIndex < 0) stripping = false
else reverseTxList.splice(txIndex, 1)
if (transactions.length <= 40) return newState
let reverseTxList = transactions.reverse()
let stripping = true
while (reverseTxList.length > 40 && stripping) {
let txIndex = reverseTxList.findIndex((txMeta) => {
return (txMeta.status === 'failed' ||
txMeta.status === 'rejected' ||
txMeta.status === 'confirmed' ||
txMeta.status === 'dropped')
})
if (txIndex < 0) stripping = false
else reverseTxList.splice(txIndex, 1)
}
newState.TransactionController.transactions = reverseTxList.reverse()
}
newState.TransactionController.transactions = reverseTxList.reverse()
return newState
}

View File

@ -0,0 +1,41 @@
const version = 24
/*
This migration ensures that the from address in txParams is to lower case for
all unapproved transactions
*/
const clone = require('clone')
module.exports = {
version,
migrate: async function (originalVersionedData) {
const versionedData = clone(originalVersionedData)
versionedData.meta.version = version
const state = versionedData.data
const newState = transformState(state)
versionedData.data = newState
return versionedData
},
}
function transformState (state) {
const newState = state
if (!newState.TransactionController) return newState
const transactions = newState.TransactionController.transactions
newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => {
if (
txMeta.status === 'unapproved' &&
txMeta.txParams &&
txMeta.txParams.from
) {
txMeta.txParams.from = txMeta.txParams.from.toLowerCase()
}
return txMeta
})
return newState
}

View File

@ -0,0 +1,61 @@
// next version number
const version = 25
/*
normalizes txParams on unconfirmed txs
*/
const ethUtil = require('ethereumjs-util')
const clone = require('clone')
module.exports = {
version,
migrate: async function (originalVersionedData) {
const versionedData = clone(originalVersionedData)
versionedData.meta.version = version
const state = versionedData.data
const newState = transformState(state)
versionedData.data = newState
return versionedData
},
}
function transformState (state) {
const newState = state
if (newState.TransactionController) {
if (newState.TransactionController.transactions) {
const transactions = newState.TransactionController.transactions
newState.TransactionController.transactions = transactions.map((txMeta) => {
if (txMeta.status !== 'unapproved') return txMeta
txMeta.txParams = normalizeTxParams(txMeta.txParams)
return txMeta
})
}
}
return newState
}
function normalizeTxParams (txParams) {
// functions that handle normalizing of that key in txParams
const whiteList = {
from: from => ethUtil.addHexPrefix(from).toLowerCase(),
to: to => ethUtil.addHexPrefix(txParams.to).toLowerCase(),
nonce: nonce => ethUtil.addHexPrefix(nonce),
value: value => ethUtil.addHexPrefix(value),
data: data => ethUtil.addHexPrefix(data),
gas: gas => ethUtil.addHexPrefix(gas),
gasPrice: gasPrice => ethUtil.addHexPrefix(gasPrice),
}
// apply only keys in the whiteList
const normalizedTxParams = {}
Object.keys(whiteList).forEach((key) => {
if (txParams[key]) normalizedTxParams[key] = whiteList[key](txParams[key])
})
return normalizedTxParams
}

View File

@ -34,4 +34,6 @@ module.exports = [
require('./021'),
require('./022'),
require('./023'),
require('./024'),
require('./025'),
]

View File

@ -0,0 +1,29 @@
// next version number
const version = 0
/*
description of migration and what it does
*/
const clone = require('clone')
module.exports = {
version,
migrate: async function (originalVersionedData) {
const versionedData = clone(originalVersionedData)
versionedData.meta.version = version
const state = versionedData.data
const newState = transformState(state)
versionedData.data = newState
return versionedData
},
}
function transformState (state) {
const newState = state
// transform state here
return newState
}

View File

@ -36,15 +36,28 @@ log.setLevel('debug')
//
const qs = require('qs')
let queryString = qs.parse(window.location.href.split('#')[1])
let selectedView = queryString.view || 'first time'
const routerPath = window.location.href.split('#')[1]
let queryString = {}
let selectedView
if (routerPath) {
queryString = qs.parse(routerPath.split('?')[1])
}
selectedView = queryString.view || 'first time'
const firstState = states[selectedView]
updateQueryParams(selectedView)
function updateQueryParams(newView) {
function updateQueryParams (newView) {
queryString.view = newView
const params = qs.stringify(queryString)
window.location.href = window.location.href.split('#')[0] + `#${params}`
const locationPaths = window.location.href.split('#')
const routerPath = locationPaths[1] || ''
const newPath = locationPaths[0] + '#' + routerPath.split('?')[0] + `?${params}`
if (window.location.href !== newPath) {
window.location.href = newPath
}
}
//

48
docs/QA_Guide.md Normal file
View File

@ -0,0 +1,48 @@
# QA Guide
Steps to mark a full pass of QA complete.
* Browsers: Opera, Chrome, Firefox, Edge.
* OS: Ubuntu, Mac OSX, Windows
* Load older version of MetaMask and attempt to simulate updating the extension.
* Open Developer Console in background and popup, inspect errors.
* Watch the state logs
* Transactions (unapproved txs -> rejected/submitted -> confirmed)
* Nonces/LocalNonces
* Vault integrity
* create vault
* Log out
* Log in again
* Log out
* Restore from seed
* Create a second account
* Import a loose account (not related to HD Wallet)
* Import old existing vault seed phrase (pref with test Ether)
* Download State Logs, Priv key file, seed phrase file.
* Send Ether
* by address
* by ens name
* Web3 API Stability
* Create a contract from a Ðapp (remix)
* Load a Ðapp that reads using events/logs (ENS)
* Connect to MEW/MyCypto
* Send a transaction from any Ðapp
- MEW
- EtherDelta
- Leeroy
- Aragon
- (https://tmashuang.github.io/demo-dapp)
* Check account balances
* Token Management
* create a token with tokenfactory (http://tokenfactory.surge.sh/#/factory)
* Add that token to the token view
* Send that token to another metamask address.
* confirm the token arrived.
* Send a transaction and sign a message (https://danfinlay.github.io/js-eth-personal-sign-examples/) for each keyring type
* hd keyring
* imported keyring
* Change network from mainnet → ropsten → rinkeby → localhost (ganache)
* Ganache set blocktime to simulate retryTx in MetaMask
* Copy public key to clipboard
* Export private key
* Explore changes in master, target features that have been changed and break.

View File

@ -0,0 +1,151 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import classnames from 'classnames'
import shuffle from 'lodash.shuffle'
import { compose } from 'recompose'
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'
class ConfirmSeedScreen extends Component {
static propTypes = {
isLoading: PropTypes.bool,
address: PropTypes.string,
seedWords: PropTypes.string,
confirmSeedWords: PropTypes.func,
history: PropTypes.object,
openBuyEtherModal: PropTypes.func,
};
static defaultProps = {
seedWords: '',
}
constructor (props) {
super(props)
const { seedWords } = props
this.state = {
selectedSeeds: [],
shuffledSeeds: seedWords && shuffle(seedWords.split(' ')) || [],
}
}
componentWillMount () {
const { seedWords, history } = this.props
if (!seedWords) {
history.push(DEFAULT_ROUTE)
}
}
handleClick () {
const { confirmSeedWords, history, openBuyEtherModal } = this.props
confirmSeedWords()
.then(() => {
history.push(DEFAULT_ROUTE)
openBuyEtherModal()
})
}
render () {
const { seedWords } = this.props
const { selectedSeeds, shuffledSeeds } = this.state
const isValid = seedWords === selectedSeeds.map(([_, seed]) => seed).join(' ')
return (
<div className="first-time-flow">
{
this.props.isLoading
? <LoadingScreen loadingMessage="Creating your new account" />
: (
<div className="first-view-main-wrapper">
<div className="first-view-main">
<div className="backup-phrase">
<Identicon address={this.props.address} diameter={70} />
<div className="backup-phrase__content-wrapper">
<div>
<div className="backup-phrase__title">
Confirm your Secret Backup Phrase
</div>
<div className="backup-phrase__body-text">
Please select each phrase in order to make sure it is correct.
</div>
<div className="backup-phrase__confirm-secret">
{selectedSeeds.map(([_, word], i) => (
<button
key={i}
className="backup-phrase__confirm-seed-option"
>
{word}
</button>
))}
</div>
<div className="backup-phrase__confirm-seed-options">
{shuffledSeeds.map((word, i) => {
const isSelected = selectedSeeds
.filter(([index, seed]) => seed === word && index === i)
.length
return (
<button
key={i}
className={classnames('backup-phrase__confirm-seed-option', {
'backup-phrase__confirm-seed-option--selected': isSelected,
})}
onClick={() => {
if (!isSelected) {
this.setState({
selectedSeeds: [...selectedSeeds, [i, word]],
})
} else {
this.setState({
selectedSeeds: selectedSeeds
.filter(([index, seed]) => !(seed === word && index === i)),
})
}
}}
>
{word}
</button>
)
})}
</div>
<button
className="first-time-flow__button"
onClick={() => isValid && this.handleClick()}
disabled={!isValid}
>
Confirm
</button>
</div>
</div>
<Breadcrumbs total={3} currentIndex={1} />
</div>
</div>
</div>
)
}
</div>
)
}
}
export default compose(
withRouter,
connect(
({ metamask: { selectedAddress, seedWords }, appState: { isLoading } }) => ({
seedWords,
isLoading,
address: selectedAddress,
}),
dispatch => ({
confirmSeedWords: () => dispatch(confirmSeedWords()),
openBuyEtherModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER'})),
})
)
)(ConfirmSeedScreen)

View File

@ -1,20 +1,26 @@
import EventEmitter from 'events'
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import classnames from 'classnames'
import {createNewVaultAndKeychain} from '../../../../ui/app/actions'
import LoadingScreen from './loading-screen'
import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
import { createNewVaultAndKeychain } from '../../../../ui/app/actions'
import Breadcrumbs from './breadcrumbs'
import EventEmitter from 'events'
import Mascot from '../../../../ui/app/components/mascot'
import classnames from 'classnames'
import {
INITIALIZE_UNIQUE_IMAGE_ROUTE,
INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE,
INITIALIZE_NOTICE_ROUTE,
} from '../../../../ui/app/routes'
class CreatePasswordScreen extends Component {
static propTypes = {
isLoading: PropTypes.bool.isRequired,
createAccount: PropTypes.func.isRequired,
goToImportWithSeedPhrase: PropTypes.func.isRequired,
goToImportAccount: PropTypes.func.isRequired,
next: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
isInitialized: PropTypes.bool,
isUnlocked: PropTypes.bool,
isMascara: PropTypes.bool.isRequired,
}
@ -23,13 +29,21 @@ class CreatePasswordScreen extends Component {
confirmPassword: '',
}
constructor () {
super()
constructor (props) {
super(props)
this.animationEventEmitter = new EventEmitter()
}
componentWillMount () {
const { isInitialized, history } = this.props
if (isInitialized) {
history.push(INITIALIZE_NOTICE_ROUTE)
}
}
isValid () {
const {password, confirmPassword} = this.state
const { password, confirmPassword } = this.state
if (!password || !confirmPassword) {
return false
@ -47,93 +61,182 @@ class CreatePasswordScreen extends Component {
return
}
const {password} = this.state
const {createAccount, next} = this.props
const { password } = this.state
const { createAccount, history } = this.props
this.setState({ isLoading: true })
createAccount(password)
.then(next)
.then(() => history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE))
}
renderFields () {
const { isMascara, history } = this.props
return (
<div className={classnames({ 'first-view-main-wrapper': !isMascara })}>
<div className={classnames({
'first-view-main': !isMascara,
'first-view-main__mascara': isMascara,
})}>
{isMascara && <div className="mascara-info first-view-phone-invisible">
<Mascot
animationEventEmitter={this.animationEventEmitter}
width="225"
height="225"
/>
<div className="info">
MetaMask is a secure identity vault for Ethereum.
</div>
<div className="info">
It allows you to hold ether & tokens, and interact with decentralized applications.
</div>
</div>}
<div className="create-password">
<div className="create-password__title">
Create Password
</div>
<input
className="first-time-flow__input"
type="password"
placeholder="New Password (min 8 characters)"
onChange={e => this.setState({password: e.target.value})}
/>
<input
className="first-time-flow__input create-password__confirm-input"
type="password"
placeholder="Confirm Password"
onChange={e => this.setState({confirmPassword: e.target.value})}
/>
<button
className="first-time-flow__button"
disabled={!this.isValid()}
onClick={this.createAccount}
>
Create
</button>
<a
href=""
className="first-time-flow__link create-password__import-link"
onClick={e => {
e.preventDefault()
history.push(INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE)
}}
>
Import with seed phrase
</a>
{ /* }
<a
href=""
className="first-time-flow__link create-password__import-link"
onClick={e => {
e.preventDefault()
history.push(INITIALIZE_IMPORT_ACCOUNT_ROUTE)
}}
>
Import an account
</a>
{ */ }
<Breadcrumbs total={3} currentIndex={0} />
</div>
</div>
</div>
)
}
render () {
const { isLoading, goToImportWithSeedPhrase, isMascara } = this.props
const { history, isMascara } = this.props
return isLoading
? <LoadingScreen loadingMessage="Creating your new account" />
: (
<div className={classnames({ 'first-view-main-wrapper': !isMascara })}>
<div className={classnames({
'first-view-main': !isMascara,
'first-view-main__mascara': isMascara,
})}>
{isMascara && <div className="mascara-info first-view-phone-invisible">
<Mascot
animationEventEmitter={this.animationEventEmitter}
width="225"
height="225"
/>
<div className="info">
MetaMask is a secure identity vault for Ethereum.
</div>
<div className="info">
It allows you to hold ether & tokens, and interact with decentralized applications.
</div>
</div>}
<div className="create-password">
<div className="create-password__title">
Create Password
</div>
<input
className="first-time-flow__input"
type="password"
placeholder="New Password (min 8 characters)"
onChange={e => this.setState({password: e.target.value})}
/>
<input
className="first-time-flow__input create-password__confirm-input"
type="password"
placeholder="Confirm Password"
onChange={e => this.setState({confirmPassword: e.target.value})}
/>
<button
className="first-time-flow__button"
disabled={!this.isValid()}
onClick={this.createAccount}
>
Create
</button>
<a
href=""
className="first-time-flow__link create-password__import-link"
onClick={e => {
e.preventDefault()
goToImportWithSeedPhrase()
}}
>
Import with seed phrase
</a>
{ /* }
<a
href=""
className="first-time-flow__link create-password__import-link"
onClick={e => {
e.preventDefault()
goToImportAccount()
}}
>
Import an account
</a>
{ */ }
<Breadcrumbs total={3} currentIndex={0} />
return (
<div className={classnames({ 'first-view-main-wrapper': !isMascara })}>
<div className={classnames({
'first-view-main': !isMascara,
'first-view-main__mascara': isMascara,
})}>
{isMascara && <div className="mascara-info first-view-phone-invisible">
<Mascot
animationEventEmitter={this.animationEventEmitter}
width="225"
height="225"
/>
<div className="info">
MetaMask is a secure identity vault for Ethereum.
</div>
<div className="info">
It allows you to hold ether & tokens, and interact with decentralized applications.
</div>
</div>}
<div className="create-password">
<div className="create-password__title">
Create Password
</div>
<input
className="first-time-flow__input"
type="password"
placeholder="New Password (min 8 characters)"
onChange={e => this.setState({password: e.target.value})}
/>
<input
className="first-time-flow__input create-password__confirm-input"
type="password"
placeholder="Confirm Password"
onChange={e => this.setState({confirmPassword: e.target.value})}
/>
<button
className="first-time-flow__button"
disabled={!this.isValid()}
onClick={this.createAccount}
>
Create
</button>
<a
href=""
className="first-time-flow__link create-password__import-link"
onClick={e => {
e.preventDefault()
history.push(INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE)
}}
>
Import with seed phrase
</a>
{ /* }
<a
href=""
className="first-time-flow__link create-password__import-link"
onClick={e => {
e.preventDefault()
history.push(INITIALIZE_IMPORT_ACCOUNT_ROUTE)
}}
>
Import an account
</a>
{ */ }
<Breadcrumbs total={3} currentIndex={0} />
</div>
</div>
)
</div>
)
}
}
export default connect(
({ appState: { isLoading }, metamask: { isMascara } }) => ({ isLoading, isMascara }),
dispatch => ({
createAccount: password => dispatch(createNewVaultAndKeychain(password)),
})
const mapStateToProps = ({ metamask, appState }) => {
const { isInitialized, isUnlocked, isMascara, noActiveNotices } = metamask
const { isLoading } = appState
return {
isLoading,
isInitialized,
isUnlocked,
isMascara,
noActiveNotices,
}
}
export default compose(
withRouter,
connect(
mapStateToProps,
dispatch => ({
createAccount: password => dispatch(createNewVaultAndKeychain(password)),
})
)
)(CreatePasswordScreen)

View File

@ -8,16 +8,16 @@ import {
displayWarning,
unMarkPasswordForgotten,
} from '../../../../ui/app/actions'
import { DEFAULT_ROUTE, INITIALIZE_NOTICE_ROUTE } from '../../../../ui/app/routes'
class ImportSeedPhraseScreen extends Component {
static propTypes = {
warning: PropTypes.string,
back: PropTypes.func.isRequired,
next: PropTypes.func.isRequired,
createNewVaultAndRestore: PropTypes.func.isRequired,
hideWarning: PropTypes.func.isRequired,
displayWarning: PropTypes.func,
leaveImportSeedScreenState: PropTypes.func,
history: PropTypes.object,
};
state = {
@ -64,14 +64,14 @@ class ImportSeedPhraseScreen extends Component {
const { password, seedPhrase } = this.state
const {
createNewVaultAndRestore,
next,
displayWarning,
leaveImportSeedScreenState,
history,
} = this.props
leaveImportSeedScreenState()
createNewVaultAndRestore(password, this.parseSeedPhrase(seedPhrase))
.then(next)
.then(() => history.push(INITIALIZE_NOTICE_ROUTE))
}
render () {
@ -86,7 +86,7 @@ class ImportSeedPhraseScreen extends Component {
className="import-account__back-button"
onClick={e => {
e.preventDefault()
this.props.back()
this.props.history.goBack()
}}
href="#"
>

View File

@ -1,17 +1,26 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import { withRouter, Switch, Route } from 'react-router-dom'
import { compose } from 'recompose'
import CreatePasswordScreen from './create-password-screen'
import UniqueImageScreen from './unique-image-screen'
import NoticeScreen from './notice-screen'
import BackupPhraseScreen from './backup-phrase-screen'
import BackupPhraseScreen from './seed-screen'
import ImportAccountScreen from './import-account-screen'
import ImportSeedPhraseScreen from './import-seed-phrase-screen'
import ConfirmSeed from './confirm-seed-screen'
import {
onboardingBuyEthView,
unMarkPasswordForgotten,
showModal,
} from '../../../../ui/app/actions'
INITIALIZE_ROUTE,
INITIALIZE_IMPORT_ACCOUNT_ROUTE,
INITIALIZE_UNIQUE_IMAGE_ROUTE,
INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE,
INITIALIZE_NOTICE_ROUTE,
INITIALIZE_BACKUP_PHRASE_ROUTE,
INITIALIZE_CONFIRM_SEED_ROUTE,
INITIALIZE_CREATE_PASSWORD_ROUTE,
} from '../../../../ui/app/routes'
import WelcomeScreen from '../../../../ui/app/welcome-screen'
class FirstTimeFlow extends Component {
@ -20,6 +29,10 @@ class FirstTimeFlow extends Component {
seedWords: PropTypes.string,
address: PropTypes.string,
noActiveNotices: PropTypes.bool,
goToBuyEtherView: PropTypes.func,
isUnlocked: PropTypes.bool,
history: PropTypes.object,
welcomeScreenSeen: PropTypes.bool,
};
static defaultProps = {
@ -28,145 +41,53 @@ class FirstTimeFlow extends Component {
noActiveNotices: false,
};
static SCREEN_TYPE = {
CREATE_PASSWORD: 'create_password',
IMPORT_ACCOUNT: 'import_account',
IMPORT_SEED_PHRASE: 'import_seed_phrase',
UNIQUE_IMAGE: 'unique_image',
NOTICE: 'notice',
BACK_UP_PHRASE: 'back_up_phrase',
CONFIRM_BACK_UP_PHRASE: 'confirm_back_up_phrase',
LOADING: 'loading',
};
constructor (props) {
super(props)
this.state = {
screenType: this.getScreenType(),
}
}
setScreenType (screenType) {
this.setState({ screenType })
}
getScreenType () {
const {
isInitialized,
seedWords,
noActiveNotices,
forgottenPassword,
} = this.props
const {SCREEN_TYPE} = FirstTimeFlow
// return SCREEN_TYPE.NOTICE
if (forgottenPassword) {
return SCREEN_TYPE.IMPORT_SEED_PHRASE
}
if (!isInitialized) {
return SCREEN_TYPE.CREATE_PASSWORD
}
if (!noActiveNotices) {
return SCREEN_TYPE.NOTICE
}
if (seedWords) {
return SCREEN_TYPE.BACK_UP_PHRASE
}
};
renderScreen () {
const {SCREEN_TYPE} = FirstTimeFlow
const {
openBuyEtherModal,
address,
restoreCreatePasswordScreen,
forgottenPassword,
leaveImportSeedScreenState,
} = this.props
switch (this.state.screenType) {
case SCREEN_TYPE.CREATE_PASSWORD:
return (
<CreatePasswordScreen
next={() => this.setScreenType(SCREEN_TYPE.UNIQUE_IMAGE)}
goToImportAccount={() => this.setScreenType(SCREEN_TYPE.IMPORT_ACCOUNT)}
goToImportWithSeedPhrase={() => this.setScreenType(SCREEN_TYPE.IMPORT_SEED_PHRASE)}
/>
)
case SCREEN_TYPE.IMPORT_ACCOUNT:
return (
<ImportAccountScreen
back={() => this.setScreenType(SCREEN_TYPE.CREATE_PASSWORD)}
next={() => this.setScreenType(SCREEN_TYPE.NOTICE)}
/>
)
case SCREEN_TYPE.IMPORT_SEED_PHRASE:
return (
<ImportSeedPhraseScreen
back={() => {
leaveImportSeedScreenState()
this.setScreenType(SCREEN_TYPE.CREATE_PASSWORD)
}}
next={() => {
const newScreenType = forgottenPassword ? null : SCREEN_TYPE.NOTICE
this.setScreenType(newScreenType)
}}
/>
)
case SCREEN_TYPE.UNIQUE_IMAGE:
return (
<UniqueImageScreen
next={() => this.setScreenType(SCREEN_TYPE.NOTICE)}
/>
)
case SCREEN_TYPE.NOTICE:
return (
<NoticeScreen
next={() => this.setScreenType(SCREEN_TYPE.BACK_UP_PHRASE)}
/>
)
case SCREEN_TYPE.BACK_UP_PHRASE:
return (
<BackupPhraseScreen
next={() => openBuyEtherModal()}
/>
)
default:
return <noscript />
}
}
render () {
return (
<div className="first-time-flow">
{this.renderScreen()}
<Switch>
<Route exact path={INITIALIZE_IMPORT_ACCOUNT_ROUTE} component={ImportAccountScreen} />
<Route
exact
path={INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE}
component={ImportSeedPhraseScreen}
/>
<Route exact path={INITIALIZE_UNIQUE_IMAGE_ROUTE} component={UniqueImageScreen} />
<Route exact path={INITIALIZE_NOTICE_ROUTE} component={NoticeScreen} />
<Route exact path={INITIALIZE_BACKUP_PHRASE_ROUTE} component={BackupPhraseScreen} />
<Route exact path={INITIALIZE_CONFIRM_SEED_ROUTE} component={ConfirmSeed} />
<Route exact path={INITIALIZE_CREATE_PASSWORD_ROUTE} component={CreatePasswordScreen} />
<Route exact path={INITIALIZE_ROUTE} component={WelcomeScreen} />
</Switch>
</div>
)
}
}
export default connect(
({
metamask: {
isInitialized,
seedWords,
noActiveNotices,
selectedAddress,
forgottenPassword,
}
}) => ({
const mapStateToProps = ({ metamask }) => {
const {
isInitialized,
seedWords,
noActiveNotices,
selectedAddress,
forgottenPassword,
isMascara,
isUnlocked,
welcomeScreenSeen,
} = metamask
return {
isMascara,
isInitialized,
seedWords,
noActiveNotices,
address: selectedAddress,
forgottenPassword,
}),
dispatch => ({
leaveImportSeedScreenState: () => dispatch(unMarkPasswordForgotten()),
openBuyEtherModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER'})),
})
isUnlocked,
welcomeScreenSeen,
}
}
export default compose(
withRouter,
connect(mapStateToProps)
)(FirstTimeFlow)

View File

@ -1,11 +1,14 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Markdown from 'react-markdown'
import {connect} from 'react-redux'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
import debounce from 'lodash.debounce'
import {markNoticeRead} from '../../../../ui/app/actions'
import { markNoticeRead } from '../../../../ui/app/actions'
import Identicon from '../../../../ui/app/components/identicon'
import Breadcrumbs from './breadcrumbs'
import { INITIALIZE_BACKUP_PHRASE_ROUTE } from '../../../../ui/app/routes'
import LoadingScreen from './loading-screen'
class NoticeScreen extends Component {
@ -16,8 +19,15 @@ class NoticeScreen extends Component {
date: PropTypes.string,
body: PropTypes.string,
}),
next: PropTypes.func.isRequired,
location: PropTypes.shape({
state: PropTypes.shape({
next: PropTypes.func.isRequired,
}),
}),
markNoticeRead: PropTypes.func,
history: PropTypes.object,
isLoading: PropTypes.bool,
noActiveNotices: PropTypes.bool,
};
static defaultProps = {
@ -29,17 +39,24 @@ class NoticeScreen extends Component {
}
componentDidMount () {
if (this.props.noActiveNotices) {
this.props.history.push(INITIALIZE_BACKUP_PHRASE_ROUTE)
}
this.onScroll()
}
acceptTerms = () => {
const { markNoticeRead, lastUnreadNotice, next } = this.props
const defer = markNoticeRead(lastUnreadNotice)
.then(() => this.setState({ atBottom: false }))
if ((/terms/gi).test(lastUnreadNotice.title)) {
defer.then(next)
}
const { markNoticeRead, lastUnreadNotice, history } = this.props
markNoticeRead(lastUnreadNotice)
.then(hasActiveNotices => {
if (!hasActiveNotices) {
history.push(INITIALIZE_BACKUP_PHRASE_ROUTE)
} else {
this.setState({ atBottom: false })
this.onScroll()
}
})
}
onScroll = debounce(() => {
@ -64,27 +81,29 @@ class NoticeScreen extends Component {
isLoading
? <LoadingScreen />
: (
<div className="first-view-main-wrapper">
<div className="first-view-main">
<div
className="tou"
onScroll={this.onScroll}
>
<Identicon address={address} diameter={70} />
<div className="tou__title">{title}</div>
<Markdown
className="tou__body markdown"
source={body}
skipHtml
/>
<button
className="first-time-flow__button"
onClick={atBottom && this.acceptTerms}
disabled={!atBottom}
<div className="first-time-flow">
<div className="first-view-main-wrapper">
<div className="first-view-main">
<div
className="tou"
onScroll={this.onScroll}
>
Accept
</button>
<Breadcrumbs total={3} currentIndex={2} />
<Identicon address={address} diameter={70} />
<div className="tou__title">{title}</div>
<Markdown
className="tou__body markdown"
source={body}
skipHtml
/>
<button
className="first-time-flow__button"
onClick={atBottom && this.acceptTerms}
disabled={!atBottom}
>
Accept
</button>
<Breadcrumbs total={3} currentIndex={2} />
</div>
</div>
</div>
</div>
@ -93,12 +112,24 @@ class NoticeScreen extends Component {
}
}
export default connect(
({ metamask: { selectedAddress, lastUnreadNotice }, appState: { isLoading } }) => ({
lastUnreadNotice,
const mapStateToProps = ({ metamask, appState }) => {
const { selectedAddress, lastUnreadNotice, noActiveNotices } = metamask
const { isLoading } = appState
return {
address: selectedAddress,
}),
dispatch => ({
markNoticeRead: notice => dispatch(markNoticeRead(notice)),
})
lastUnreadNotice,
noActiveNotices,
isLoading,
}
}
export default compose(
withRouter,
connect(
mapStateToProps,
dispatch => ({
markNoticeRead: notice => dispatch(markNoticeRead(notice)),
})
)
)(NoticeScreen)

View File

@ -1,13 +1,13 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import { connect } from 'react-redux'
import classnames from 'classnames'
import shuffle from 'lodash.shuffle'
import {compose, onlyUpdateForPropTypes} from 'recompose'
import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
import Identicon from '../../../../ui/app/components/identicon'
import {confirmSeedWords} from '../../../../ui/app/actions'
import Breadcrumbs from './breadcrumbs'
import LoadingScreen from './loading-screen'
import { DEFAULT_ROUTE, INITIALIZE_CONFIRM_SEED_ROUTE } from '../../../../ui/app/routes'
const LockIcon = props => (
<svg
@ -36,34 +36,32 @@ const LockIcon = props => (
/>
</g>
</svg>
);
)
class BackupPhraseScreen extends Component {
static propTypes = {
isLoading: PropTypes.bool.isRequired,
address: PropTypes.string.isRequired,
seedWords: PropTypes.string.isRequired,
next: PropTypes.func.isRequired,
confirmSeedWords: PropTypes.func.isRequired,
seedWords: PropTypes.string,
history: PropTypes.object,
};
static defaultProps = {
seedWords: ''
};
seedWords: '',
}
static PAGE = {
SECRET: 'secret',
CONFIRM: 'confirm'
};
constructor(props) {
const {seedWords} = props
constructor (props) {
super(props)
this.state = {
isShowingSecret: false,
page: BackupPhraseScreen.PAGE.SECRET,
selectedSeeds: [],
shuffledSeeds: seedWords && shuffle(seedWords.split(' ')),
}
}
componentWillMount () {
const { seedWords, history } = this.props
if (!seedWords) {
history.push(DEFAULT_ROUTE)
}
}
@ -73,7 +71,7 @@ class BackupPhraseScreen extends Component {
return (
<div className="backup-phrase__secret">
<div className={classnames('backup-phrase__secret-words', {
'backup-phrase__secret-words--hidden': !isShowingSecret
'backup-phrase__secret-words--hidden': !isShowingSecret,
})}>
{this.props.seedWords}
</div>
@ -96,6 +94,7 @@ class BackupPhraseScreen extends Component {
renderSecretScreen () {
const { isShowingSecret } = this.state
const { history } = this.props
return (
<div className="backup-phrase__content-wrapper">
@ -124,10 +123,7 @@ class BackupPhraseScreen extends Component {
<div className="backup-phrase__next-button">
<button
className="first-time-flow__button"
onClick={() => isShowingSecret && this.setState({
isShowingSecret: false,
page: BackupPhraseScreen.PAGE.CONFIRM,
})}
onClick={() => isShowingSecret && history.push(INITIALIZE_CONFIRM_SEED_ROUTE)}
disabled={!isShowingSecret}
>
Next
@ -138,99 +134,6 @@ class BackupPhraseScreen extends Component {
)
}
renderConfirmationScreen() {
const { seedWords, confirmSeedWords, next } = this.props;
const { selectedSeeds, shuffledSeeds } = this.state;
const isValid = seedWords === selectedSeeds.map(([_, seed]) => seed).join(' ')
return (
<div className="backup-phrase__content-wrapper">
<div>
<div className="backup-phrase__title">Confirm your Secret Backup Phrase</div>
<div className="backup-phrase__body-text">
Please select each phrase in order to make sure it is correct.
</div>
<div className="backup-phrase__confirm-secret">
{selectedSeeds.map(([_, word], i) => (
<button
key={i}
className="backup-phrase__confirm-seed-option"
>
{word}
</button>
))}
</div>
<div className="backup-phrase__confirm-seed-options">
{shuffledSeeds.map((word, i) => {
const isSelected = selectedSeeds
.filter(([index, seed]) => seed === word && index === i)
.length
return (
<button
key={i}
className={classnames('backup-phrase__confirm-seed-option', {
'backup-phrase__confirm-seed-option--selected': isSelected
})}
onClick={() => {
if (!isSelected) {
this.setState({
selectedSeeds: [...selectedSeeds, [i, word]]
})
} else {
this.setState({
selectedSeeds: selectedSeeds
.filter(([index, seed]) => !(seed === word && index === i))
})
}
}}
>
{word}
</button>
)
})}
</div>
<button
className="first-time-flow__button"
onClick={() => isValid && confirmSeedWords().then(next)}
disabled={!isValid}
>
Confirm
</button>
</div>
</div>
)
}
renderBack () {
return this.state.page === BackupPhraseScreen.PAGE.CONFIRM
? (
<a
className="backup-phrase__back-button"
onClick={e => {
e.preventDefault()
this.setState({
page: BackupPhraseScreen.PAGE.SECRET
})
}}
href="#"
>
{`< Back`}
</a>
)
: null
}
renderContent () {
switch (this.state.page) {
case BackupPhraseScreen.PAGE.CONFIRM:
return this.renderConfirmationScreen()
case BackupPhraseScreen.PAGE.SECRET:
default:
return this.renderSecretScreen()
}
}
render () {
return this.props.isLoading
? <LoadingScreen loadingMessage="Creating your new account" />
@ -238,9 +141,8 @@ class BackupPhraseScreen extends Component {
<div className="first-view-main-wrapper">
<div className="first-view-main">
<div className="backup-phrase">
{this.renderBack()}
<Identicon address={this.props.address} diameter={70} />
{this.renderContent()}
{this.renderSecretScreen()}
</div>
</div>
</div>
@ -249,15 +151,12 @@ class BackupPhraseScreen extends Component {
}
export default compose(
onlyUpdateForPropTypes,
withRouter,
connect(
({ metamask: { selectedAddress, seedWords }, appState: { isLoading } }) => ({
seedWords,
isLoading,
address: selectedAddress,
}),
dispatch => ({
confirmSeedWords: () => dispatch(confirmSeedWords()),
})
)
)(BackupPhraseScreen)

View File

@ -1,13 +1,16 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
import {connect} from 'react-redux'
import Identicon from '../../../../ui/app/components/identicon'
import Breadcrumbs from './breadcrumbs'
import { INITIALIZE_NOTICE_ROUTE } from '../../../../ui/app/routes'
class UniqueImageScreen extends Component {
static propTypes = {
address: PropTypes.string,
next: PropTypes.func.isRequired,
history: PropTypes.object,
}
render () {
@ -25,7 +28,7 @@ class UniqueImageScreen extends Component {
</div>
<button
className="first-time-flow__button"
onClick={this.props.next}
onClick={() => this.props.history.push(INITIALIZE_NOTICE_ROUTE)}
>
Next
</button>
@ -37,8 +40,11 @@ class UniqueImageScreen extends Component {
}
}
export default connect(
({ metamask: { selectedAddress } }) => ({
address: selectedAddress,
})
export default compose(
withRouter,
connect(
({ metamask: { selectedAddress } }) => ({
address: selectedAddress,
})
)
)(UniqueImageScreen)

View File

@ -231,6 +231,7 @@ function exportAsFile (filename, data) {
window.navigator.msSaveBlob(blob, filename)
} else {
const elem = window.document.createElement('a')
elem.target = '_blank'
elem.href = window.URL.createObjectURL(blob)
elem.download = filename
document.body.appendChild(elem)

98
package-lock.json generated
View File

@ -5112,7 +5112,7 @@
"lodash": "4.17.4",
"object.assign": "4.1.0",
"object.values": "1.0.4",
"prop-types": "15.6.0"
"prop-types": "15.6.1"
}
},
"enzyme-adapter-utils": {
@ -5123,7 +5123,7 @@
"requires": {
"lodash": "4.17.4",
"object.assign": "4.1.0",
"prop-types": "15.6.0"
"prop-types": "15.6.1"
}
},
"errno": {
@ -5454,7 +5454,7 @@
"doctrine": "2.0.2",
"has": "1.0.1",
"jsx-ast-utils": "2.0.1",
"prop-types": "15.6.0"
"prop-types": "15.6.1"
}
},
"eslint-scope": {
@ -10394,6 +10394,18 @@
"request": "2.83.0"
}
},
"history": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz",
"integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==",
"requires": {
"invariant": "2.2.2",
"loose-envify": "1.3.1",
"resolve-pathname": "2.2.0",
"value-equal": "0.4.0",
"warning": "3.0.0"
}
},
"hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@ -18886,9 +18898,9 @@
}
},
"prop-types": {
"version": "15.6.0",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz",
"integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=",
"version": "15.6.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz",
"integrity": "sha512-4ec7bY1Y66LymSUOH/zARVYObB23AT2h8cf6e/O6ZALB/N0sqZFEx7rq6EYPX2MkOdKORuooI/H5k9TlR4q7kQ==",
"requires": {
"fbjs": "0.8.16",
"loose-envify": "1.3.1",
@ -19316,7 +19328,7 @@
"fbjs": "0.8.16",
"loose-envify": "1.3.1",
"object-assign": "4.1.1",
"prop-types": "15.6.0"
"prop-types": "15.6.1"
}
},
"react-addons-css-transition-group": {
@ -19335,7 +19347,7 @@
"chain-function": "1.0.0",
"dom-helpers": "3.3.1",
"loose-envify": "1.3.1",
"prop-types": "15.6.0",
"prop-types": "15.6.1",
"warning": "3.0.0"
}
}
@ -19355,7 +19367,7 @@
"fbjs": "0.8.16",
"loose-envify": "1.3.1",
"object-assign": "4.1.1",
"prop-types": "15.6.0"
"prop-types": "15.6.1"
}
},
"react-hyperscript": {
@ -19368,7 +19380,7 @@
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.1.2.tgz",
"integrity": "sha512-uAfIE4XEfBNXqjqQvd31Eoo20UkVk0xHJpfgP8HRT8gLczaN4LEmB1e2d8CJ5ziEt4clWnsk/1+QhTN27iO/EA==",
"requires": {
"prop-types": "15.6.0"
"prop-types": "15.6.1"
}
},
"react-markdown": {
@ -19376,7 +19388,7 @@
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-3.1.4.tgz",
"integrity": "sha512-i8WueytRXbYzyJ2GemIOTMRx/NigPo8r4m3R/KvWD7r+PxPyc9ke66cI3DR7MBRSS+nVG82VWEgRDE1VaZUCqA==",
"requires": {
"prop-types": "15.6.0",
"prop-types": "15.6.1",
"remark-parse": "4.0.0",
"unified": "6.1.6",
"unist-util-visit": "1.3.0",
@ -19389,7 +19401,7 @@
"integrity": "sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ==",
"requires": {
"performance-now": "0.2.0",
"prop-types": "15.6.0",
"prop-types": "15.6.1",
"raf": "3.4.0"
},
"dependencies": {
@ -19410,7 +19422,49 @@
"lodash": "4.17.4",
"lodash-es": "4.17.4",
"loose-envify": "1.3.1",
"prop-types": "15.6.0"
"prop-types": "15.6.1"
}
},
"react-router": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-4.2.0.tgz",
"integrity": "sha512-DY6pjwRhdARE4TDw7XjxjZsbx9lKmIcyZoZ+SDO7SBJ1KUeWNxT22Kara2AC7u6/c2SYEHlEDLnzBCcNhLE8Vg==",
"requires": {
"history": "4.7.2",
"hoist-non-react-statics": "2.3.1",
"invariant": "2.2.2",
"loose-envify": "1.3.1",
"path-to-regexp": "1.7.0",
"prop-types": "15.6.1",
"warning": "3.0.0"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"path-to-regexp": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
"integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=",
"requires": {
"isarray": "0.0.1"
}
}
}
},
"react-router-dom": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.2.2.tgz",
"integrity": "sha512-cHMFC1ZoLDfEaMFoKTjN7fry/oczMgRt5BKfMAkTu5zEuJvUiPp1J8d0eXSVTnBh6pxlbdqDhozunOOLtmKfPA==",
"requires": {
"history": "4.7.2",
"invariant": "2.2.2",
"loose-envify": "1.3.1",
"prop-types": "15.6.1",
"react-router": "4.2.0",
"warning": "3.0.0"
}
},
"react-select": {
@ -19419,7 +19473,7 @@
"integrity": "sha512-c4CdxweEHN9ra85HGWjSjIMBlJ5c0fsIXOymLFZS5UbZEQCiJGHnZTVLTt6/wDh8RKQnxl85gHUwzhG5XZLcyw==",
"requires": {
"classnames": "2.2.5",
"prop-types": "15.6.0",
"prop-types": "15.6.1",
"react-input-autosize": "2.1.2"
}
},
@ -19428,7 +19482,7 @@
"resolved": "https://registry.npmjs.org/react-simple-file-input/-/react-simple-file-input-2.0.1.tgz",
"integrity": "sha1-Fa1P/Hj+sbiCZJrWsBwDPvJ1ceY=",
"requires": {
"prop-types": "15.6.0"
"prop-types": "15.6.1"
}
},
"react-test-renderer": {
@ -19472,7 +19526,7 @@
"resolved": "https://registry.npmjs.org/react-toggle-button/-/react-toggle-button-2.2.0.tgz",
"integrity": "sha1-obkhQ6oN9BRkL8sUHwh59UW8Wok=",
"requires": {
"prop-types": "15.6.0",
"prop-types": "15.6.1",
"react-motion": "0.5.2"
}
},
@ -19499,7 +19553,7 @@
"classnames": "2.2.5",
"dom-helpers": "3.3.1",
"loose-envify": "1.3.1",
"prop-types": "15.6.0",
"prop-types": "15.6.1",
"warning": "3.0.0"
}
},
@ -20058,6 +20112,11 @@
"value-or-function": "3.0.0"
}
},
"resolve-pathname": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz",
"integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg=="
},
"resolve-url": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@ -23736,6 +23795,11 @@
"spdx-expression-parse": "1.0.4"
}
},
"value-equal": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz",
"integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw=="
},
"value-or-function": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz",

View File

@ -100,7 +100,7 @@
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
"ethereumjs-wallet": "^0.6.0",
"etherscan-link": "^1.0.2",
"ethjs": "^0.2.8",
"ethjs": "^0.3.4",
"ethjs-contract": "^0.1.9",
"ethjs-ens": "^2.0.0",
"ethjs-query": "^0.3.4",
@ -114,7 +114,7 @@
"gulp-autoprefixer": "^5.0.0",
"gulp-debug": "^3.2.0",
"gulp-eslint": "^4.0.0",
"gulp-sass": "^3.1.0",
"gulp-sass": "^4.0.0",
"hat": "0.0.3",
"human-standard-token-abi": "^1.0.2",
"idb-global": "^2.1.0",
@ -145,6 +145,7 @@
"post-message-stream": "^3.0.0",
"promise-filter": "^1.1.0",
"promise-to-callback": "^1.0.0",
"prop-types": "^15.6.1",
"pump": "^3.0.0",
"pumpify": "^1.3.4",
"qrcode-npm": "0.0.3",
@ -156,6 +157,7 @@
"react-hyperscript": "^3.0.0",
"react-markdown": "^3.0.0",
"react-redux": "^5.0.5",
"react-router-dom": "^4.2.2",
"react-select": "^1.0.0",
"react-simple-file-input": "^2.0.0",
"react-tippy": "^1.2.2",

View File

@ -75,7 +75,7 @@ async function runAddTokenFlowTest (assert, done) {
// Confirm Add token
assert.equal(
$('.add-token__description')[0].textContent,
'Would you like to add these tokens?',
'Token balance(s)',
'confirm add token rendered'
)
assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found')

View File

@ -21,11 +21,22 @@ async function runConfirmSigRequestsTest(assert, done) {
selectState.val('confirm sig requests')
reactTriggerChange(selectState[0])
// await timeout(1000000)
const pendingRequestItem = $.find('.tx-list-item.tx-list-pending-item-container.tx-list-clickable')
if (pendingRequestItem[0]) {
pendingRequestItem[0].click()
}
let confirmSigHeadline = await queryAsync($, '.request-signature__headline')
assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested')
let confirmSigMessage = await queryAsync($, '.request-signature__notice')
assert.ok(confirmSigMessage[0].textContent.match(/^Signing\sthis\smessage/))
let confirmSigRowValue = await queryAsync($, '.request-signature__row-value')
assert.ok(confirmSigRowValue[0].textContent.match(/^\#\sTerms\sof\sUse/))
assert.equal(confirmSigRowValue[0].textContent, '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0')
let confirmSigSignButton = await queryAsync($, 'button.btn-primary--lg')
confirmSigSignButton[0].click()
@ -33,11 +44,8 @@ async function runConfirmSigRequestsTest(assert, done) {
confirmSigHeadline = await queryAsync($, '.request-signature__headline')
assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested')
let confirmSigMessage = await queryAsync($, '.request-signature__notice')
assert.ok(confirmSigMessage[0].textContent.match(/^Signing\sthis\smessage/))
confirmSigRowValue = await queryAsync($, '.request-signature__row-value')
assert.equal(confirmSigRowValue[0].textContent, '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0')
assert.ok(confirmSigRowValue[0].textContent.match(/^\#\sTerms\sof\sUse/))
confirmSigSignButton = await queryAsync($, 'button.btn-primary--lg')
confirmSigSignButton[0].click()

View File

@ -13,6 +13,9 @@ async function runFirstTimeUsageTest (assert, done) {
await skipNotices(app)
const welcomeButton = (await findAsync(app, '.welcome-screen__button'))[0]
welcomeButton.click()
// Scroll through terms
const title = (await findAsync(app, '.create-password__title')).text()
assert.equal(title, 'Create Password', 'create password screen')

View File

@ -53,7 +53,7 @@ async function runSendFlowTest(assert, done) {
assert.equal(sendFromDropdownList.children().length, 4, 'send from dropdown shows all accounts')
sendFromDropdownList.children()[1].click()
sendFromFieldItemAddress = await queryAsync($, '.account-list-item__account-name')
sendFromFieldItemAddress = await queryAsync($, '.account-list-item__account-name')
assert.equal(sendFromFieldItemAddress[0].textContent, 'Send Account 2', 'send from field dropdown changes account name')
let sendToFieldInput = await queryAsync($, '.send-v2__to-autocomplete__input')
@ -164,17 +164,27 @@ async function runSendFlowTest(assert, done) {
const sendButtonInEdit = await queryAsync($, '.btn-primary--lg.page-container__footer-button')
assert.equal(sendButtonInEdit[0].textContent, 'Next', 'next button in edit rendered')
sendButtonInEdit[0].click()
// TODO: Need a way to mock background so that we can test correct transition from editing to confirm
selectState.val('confirm new ui')
selectState.val('send new ui')
reactTriggerChange(selectState[0])
const confirmScreenConfirmButton = await queryAsync($, '.btn-confirm.page-container__footer-button')
console.log(`+++++++++++++++++++++++++++++++= confirmScreenConfirmButton[0]`, confirmScreenConfirmButton[0]);
confirmScreenConfirmButton[0].click()
const txView = await queryAsync($, '.tx-view')
console.log(`++++++++++++++++++++++++++++++++ txView[0]`, txView[0]);
const cancelButtonInEdit = await queryAsync($, '.btn-secondary--lg.page-container__footer-button')
cancelButtonInEdit[0].click()
// sendButtonInEdit[0].click()
assert.ok(txView[0], 'Should return to the account details screen after confirming')
// // TODO: Need a way to mock background so that we can test correct transition from editing to confirm
// selectState.val('confirm new ui')
// reactTriggerChange(selectState[0])
// const confirmScreenConfirmButton = await queryAsync($, '.btn-confirm.page-container__footer-button')
// console.log(`+++++++++++++++++++++++++++++++= confirmScreenConfirmButton[0]`, confirmScreenConfirmButton[0]);
// confirmScreenConfirmButton[0].click()
// await timeout(10000000)
// const txView = await queryAsync($, '.tx-view')
// console.log(`++++++++++++++++++++++++++++++++ txView[0]`, txView[0]);
// assert.ok(txView[0], 'Should return to the account details screen after confirming')
}

View File

@ -0,0 +1,49 @@
const assert = require('assert')
const migration24 = require('../../../app/scripts/migrations/024')
const firstTimeState = {
meta: {},
data: require('../../../app/scripts/first-time-state'),
}
const properTime = (new Date()).getTime()
const storage = {
"meta": {},
"data": {
"TransactionController": {
"transactions": [
]
},
},
}
const transactions = []
while (transactions.length <= 10) {
transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675' }, status: 'unapproved' })
transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675' }, status: 'confirmed' })
}
storage.data.TransactionController.transactions = transactions
describe('storage is migrated successfully and the txParams.from are lowercase', () => {
it('should lowercase the from for unapproved txs', (done) => {
migration24.migrate(storage)
.then((migratedData) => {
const migratedTransactions = migratedData.data.TransactionController.transactions
migratedTransactions.forEach((tx) => {
if (tx.status === 'unapproved') assert.equal(tx.txParams.from, '0x8acce2391c0d510a6c5e5d8f819a678f79b7e675')
else assert.equal(tx.txParams.from, '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675')
})
done()
}).catch(done)
})
it('should migrate first time state', (done) => {
migration24.migrate(firstTimeState)
.then((migratedData) => {
assert.equal(migratedData.meta.version, 24)
done()
}).catch(done)
})
})

View File

@ -0,0 +1,49 @@
const assert = require('assert')
const migration25 = require('../../../app/scripts/migrations/025')
const firstTimeState = {
meta: {},
data: require('../../../app/scripts/first-time-state'),
}
const storage = {
"meta": {},
"data": {
"TransactionController": {
"transactions": [
]
},
},
}
const transactions = []
while (transactions.length <= 10) {
transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675', random: 'stuff', chainId: 2 }, status: 'unapproved' })
transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675' }, status: 'confirmed' })
}
storage.data.TransactionController.transactions = transactions
describe('storage is migrated successfully and the txParams.from are lowercase', () => {
it('should lowercase the from for unapproved txs', (done) => {
migration25.migrate(storage)
.then((migratedData) => {
const migratedTransactions = migratedData.data.TransactionController.transactions
migratedTransactions.forEach((tx) => {
if (tx.status === 'unapproved') assert(!tx.txParams.random)
if (tx.status === 'unapproved') assert(!tx.txParams.chainId)
})
done()
}).catch(done)
})
it('should migrate first time state', (done) => {
migration25.migrate(firstTimeState)
.then((migratedData) => {
assert.equal(migratedData.meta.version, 25)
done()
}).catch(done)
})
})

View File

@ -0,0 +1,17 @@
const assert = require('assert')
const migrationTemplate = require('../../../app/scripts/migrations/template')
const properTime = (new Date()).getTime()
const storage = {
meta: {},
data: {},
}
describe('storage is migrated successfully', () => {
it('should work', (done) => {
migrationTemplate.migrate(storage)
.then((migratedData) => {
assert.equal(migratedData.meta.version, 0)
done()
}).catch(done)
})
})

View File

@ -1,7 +1,8 @@
const assert = require('assert')
const clone = require('clone')
const Migrator = require('../../app/scripts/lib/migrator/')
const migrations = [
const liveMigrations = require('../../app/scripts/migrations/')
const stubMigrations = [
{
version: 1,
migrate: (data) => {
@ -29,13 +30,39 @@ const migrations = [
},
]
const versionedData = {meta: {version: 0}, data: {hello: 'world'}}
const firstTimeState = {
meta: { version: 0 },
data: require('../../app/scripts/first-time-state'),
}
describe('Migrator', () => {
const migrator = new Migrator({ migrations })
const migrator = new Migrator({ migrations: stubMigrations })
it('migratedData version should be version 3', (done) => {
migrator.migrateData(versionedData)
.then((migratedData) => {
assert.equal(migratedData.meta.version, migrations[2].version)
assert.equal(migratedData.meta.version, stubMigrations[2].version)
done()
}).catch(done)
})
it('should match the last version in live migrations', (done) => {
const migrator = new Migrator({ migrations: liveMigrations })
migrator.migrateData(firstTimeState)
.then((migratedData) => {
const last = liveMigrations.length - 1
assert.equal(migratedData.meta.version, liveMigrations[last].version)
done()
}).catch(done)
})
it('should emit an error', function (done) {
this.timeout(15000)
const migrator = new Migrator({ migrations: [{ version: 1, migrate: async () => { throw new Error('test') } } ] })
migrator.on('error', () => done())
migrator.migrateData({ meta: {version: 0} })
.then((migratedData) => {
}).catch(done)
})
})

View File

@ -210,31 +210,99 @@ describe('Transaction Controller', function () {
})
})
describe('#validateTxParams', function () {
it('does not throw for positive values', function (done) {
describe('#_validateTxParams', function () {
it('does not throw for positive values', function () {
var sample = {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
value: '0x01',
}
txController.txGasUtil.validateTxParams(sample).then(() => {
done()
}).catch(done)
txController._validateTxParams(sample)
})
it('returns error for negative values', function (done) {
it('returns error for negative values', function () {
var sample = {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
value: '-0x01',
}
txController.txGasUtil.validateTxParams(sample)
.then(() => done('expected to thrown on negativity values but didn\'t'))
.catch((err) => {
try {
txController._validateTxParams(sample)
} catch (err) {
assert.ok(err, 'error')
done()
})
}
})
})
describe('#_normalizeTxParams', () => {
it('should normalize txParams', () => {
let txParams = {
chainId: '0x1',
from: 'a7df1beDBF813f57096dF77FCd515f0B3900e402',
to: null,
data: '68656c6c6f20776f726c64',
random: 'hello world',
}
let normalizedTxParams = txController._normalizeTxParams(txParams)
assert(!normalizedTxParams.chainId, 'their should be no chainId')
assert(!normalizedTxParams.to, 'their should be no to address if null')
assert.equal(normalizedTxParams.from.slice(0, 2), '0x', 'from should be hexPrefixd')
assert.equal(normalizedTxParams.data.slice(0, 2), '0x', 'data should be hexPrefixd')
assert(!('random' in normalizedTxParams), 'their should be no random key in normalizedTxParams')
txParams.to = 'a7df1beDBF813f57096dF77FCd515f0B3900e402'
normalizedTxParams = txController._normalizeTxParams(txParams)
assert.equal(normalizedTxParams.to.slice(0, 2), '0x', 'to should be hexPrefixd')
})
})
describe('#_validateRecipient', () => {
it('removes recipient for txParams with 0x when contract data is provided', function () {
const zeroRecipientandDataTxParams = {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
to: '0x',
data: 'bytecode',
}
const sanitizedTxParams = txController._validateRecipient(zeroRecipientandDataTxParams)
assert.deepEqual(sanitizedTxParams, { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', data: 'bytecode' }, 'no recipient with 0x')
})
it('should error when recipient is 0x', function () {
const zeroRecipientTxParams = {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
to: '0x',
}
assert.throws(() => { txController._validateRecipient(zeroRecipientTxParams) }, Error, 'Invalid recipient address')
})
})
describe('#_validateFrom', () => {
it('should error when from is not a hex string', function () {
// where from is undefined
const txParams = {}
assert.throws(() => { txController._validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`)
// where from is array
txParams.from = []
assert.throws(() => { txController._validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`)
// where from is a object
txParams.from = {}
assert.throws(() => { txController._validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`)
// where from is a invalid address
txParams.from = 'im going to fail'
assert.throws(() => { txController._validateFrom(txParams) }, Error, `Invalid from address`)
// should run
txParams.from ='0x1678a085c290ebd122dc42cba69373b5953b831d'
txController._validateFrom(txParams)
})
})
describe('#addTx', function () {
it('should emit updates', function (done) {
const txMeta = {

View File

@ -11,46 +11,4 @@ describe('Tx Gas Util', function () {
provider,
})
})
it('removes recipient for txParams with 0x when contract data is provided', function () {
const zeroRecipientandDataTxParams = {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
to: '0x',
data: 'bytecode',
}
const sanitizedTxParams = txGasUtil.validateRecipient(zeroRecipientandDataTxParams)
assert.deepEqual(sanitizedTxParams, { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', data: 'bytecode' }, 'no recipient with 0x')
})
it('should error when recipient is 0x', function () {
const zeroRecipientTxParams = {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
to: '0x',
}
assert.throws(() => { txGasUtil.validateRecipient(zeroRecipientTxParams) }, Error, 'Invalid recipient address')
})
it('should error when from is not a hex string', function () {
// where from is undefined
const txParams = {}
assert.throws(() => { txGasUtil.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`)
// where from is array
txParams.from = []
assert.throws(() => { txGasUtil.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`)
// where from is a object
txParams.from = {}
assert.throws(() => { txGasUtil.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`)
// where from is a invalid address
txParams.from = 'im going to fail'
assert.throws(() => { txGasUtil.validateFrom(txParams) }, Error, `Invalid from address`)
// should run
txParams.from ='0x1678a085c290ebd122dc42cba69373b5953b831d'
txGasUtil.validateFrom(txParams)
})
})

View File

@ -347,7 +347,7 @@ function transitionBackward () {
}
function confirmSeedWords () {
return (dispatch) => {
return dispatch => {
dispatch(actions.showLoadingIndication())
log.debug(`background.clearSeedWordCache`)
return new Promise((resolve, reject) => {
@ -355,7 +355,7 @@ function confirmSeedWords () {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
reject(err)
return reject(err)
}
log.info('Seed word cache cleared. ' + account)
@ -571,35 +571,47 @@ function signMsg (msgData) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`actions calling background.signMessage`)
background.signMessage(msgData, (err, newState) => {
log.debug('signMessage called back')
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
return new Promise((resolve, reject) => {
log.debug(`actions calling background.signMessage`)
background.signMessage(msgData, (err, newState) => {
log.debug('signMessage called back')
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
if (err) log.error(err)
if (err) return dispatch(actions.displayWarning(err.message))
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.completedTx(msgData.metamaskId))
dispatch(actions.completedTx(msgData.metamaskId))
return resolve(msgData)
})
})
}
}
function signPersonalMsg (msgData) {
log.debug('action - signPersonalMsg')
return (dispatch) => {
return dispatch => {
dispatch(actions.showLoadingIndication())
log.debug(`actions calling background.signPersonalMessage`)
background.signPersonalMessage(msgData, (err, newState) => {
log.debug('signPersonalMessage called back')
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
return new Promise((resolve, reject) => {
log.debug(`actions calling background.signPersonalMessage`)
background.signPersonalMessage(msgData, (err, newState) => {
log.debug('signPersonalMessage called back')
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
if (err) log.error(err)
if (err) return dispatch(actions.displayWarning(err.message))
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.completedTx(msgData.metamaskId))
dispatch(actions.completedTx(msgData.metamaskId))
return resolve(msgData)
})
})
}
}
@ -609,16 +621,22 @@ function signTypedMsg (msgData) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`actions calling background.signTypedMessage`)
background.signTypedMessage(msgData, (err, newState) => {
log.debug('signTypedMessage called back')
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
return new Promise((resolve, reject) => {
log.debug(`actions calling background.signTypedMessage`)
background.signTypedMessage(msgData, (err, newState) => {
log.debug('signTypedMessage called back')
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
if (err) log.error(err)
if (err) return dispatch(actions.displayWarning(err.message))
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.completedTx(msgData.metamaskId))
dispatch(actions.completedTx(msgData.metamaskId))
return resolve(msgData)
})
})
}
}
@ -798,17 +816,24 @@ function updateTransaction (txData) {
function updateAndApproveTx (txData) {
log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData))
return (dispatch) => {
log.debug(`actions calling background.updateAndApproveTx.`)
background.updateAndApproveTransaction(txData, (err) => {
dispatch(actions.hideLoadingIndication())
dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
dispatch(actions.clearSend())
if (err) {
dispatch(actions.txError(err))
dispatch(actions.goHome())
return log.error(err.message)
}
dispatch(actions.completedTx(txData.id))
log.debug(`actions calling background.updateAndApproveTx`)
return new Promise((resolve, reject) => {
background.updateAndApproveTransaction(txData, err => {
dispatch(actions.hideLoadingIndication())
dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
dispatch(actions.clearSend())
if (err) {
dispatch(actions.txError(err))
dispatch(actions.goHome())
log.error(err.message)
reject(err)
}
dispatch(actions.completedTx(txData.id))
resolve(txData)
})
})
}
}
@ -836,29 +861,77 @@ function txError (err) {
}
function cancelMsg (msgData) {
log.debug(`background.cancelMessage`)
background.cancelMessage(msgData.id)
return actions.completedTx(msgData.id)
return dispatch => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
log.debug(`background.cancelMessage`)
background.cancelMessage(msgData.id, (err, newState) => {
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
if (err) {
return reject(err)
}
dispatch(actions.completedTx(msgData.id))
return resolve(msgData)
})
})
}
}
function cancelPersonalMsg (msgData) {
const id = msgData.id
background.cancelPersonalMessage(id)
return actions.completedTx(id)
return dispatch => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
const id = msgData.id
background.cancelPersonalMessage(id, (err, newState) => {
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
if (err) {
return reject(err)
}
dispatch(actions.completedTx(id))
return resolve(msgData)
})
})
}
}
function cancelTypedMsg (msgData) {
const id = msgData.id
background.cancelTypedMessage(id)
return actions.completedTx(id)
return dispatch => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
const id = msgData.id
background.cancelTypedMessage(id, (err, newState) => {
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
if (err) {
return reject(err)
}
dispatch(actions.completedTx(id))
return resolve(msgData)
})
})
}
}
function cancelTx (txData) {
return (dispatch) => {
return dispatch => {
log.debug(`background.cancelTransaction`)
background.cancelTransaction(txData.id, () => {
dispatch(actions.clearSend())
dispatch(actions.completedTx(txData.id))
return new Promise((resolve, reject) => {
background.cancelTransaction(txData.id, () => {
dispatch(actions.clearSend())
dispatch(actions.completedTx(txData.id))
resolve(txData)
})
})
}
}
@ -1249,12 +1322,13 @@ function markNoticeRead (notice) {
dispatch(actions.displayWarning(err))
return reject(err)
}
if (notice) {
dispatch(actions.showNotice(notice))
resolve()
resolve(true)
} else {
dispatch(actions.clearNotices())
resolve()
resolve(false)
}
})
})
@ -1773,7 +1847,7 @@ function forceUpdateMetamaskState (dispatch) {
}
dispatch(actions.updateMetamaskState(newState))
resolve()
resolve(newState)
})
})
}

View File

@ -1,62 +1,421 @@
const inherits = require('util').inherits
const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const { Component } = require('react')
const PropTypes = require('prop-types')
const connect = require('react-redux').connect
const { Route, Switch, withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const h = require('react-hyperscript')
const actions = require('./actions')
const classnames = require('classnames')
// mascara
const MascaraFirstTime = require('../../mascara/src/app/first-time').default
const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default
// init
const OldUIInitializeMenuScreen = require('./first-time/init-menu')
const InitializeMenuScreen = MascaraFirstTime
const NewKeyChainScreen = require('./new-keychain')
const WelcomeScreen = require('./welcome-screen').default
const InitializeScreen = require('../../mascara/src/app/first-time').default
// accounts
const MainContainer = require('./main-container')
const SendTransactionScreen2 = require('./components/send/send-v2-container')
const ConfirmTxScreen = require('./conf-tx')
// notice
const NoticeScreen = require('./components/notice')
const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
// slideout menu
const WalletView = require('./components/wallet-view')
// other views
const Settings = require('./settings')
const AddTokenScreen = require('./add-token')
const Import = require('./accounts/import')
const NewAccount = require('./accounts/new-account')
const Home = require('./components/pages/home')
const Authenticated = require('./components/pages/authenticated')
const Initialized = require('./components/pages/initialized')
const Settings = require('./components/pages/settings')
const UnlockPage = require('./components/pages/unlock')
const RestoreVaultPage = require('./components/pages/keychains/restore-vault')
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
const AddTokenPage = require('./components/pages/add-token')
const CreateAccountPage = require('./components/pages/create-account')
const NoticeScreen = require('./components/pages/notice')
const Loading = require('./components/loading')
const NetworkIndicator = require('./components/network')
const Identicon = require('./components/identicon')
const BuyView = require('./components/buy-button-subview')
const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
const NetworkDropdown = require('./components/dropdowns/network-dropdown')
const AccountMenu = require('./components/account-menu')
const QrView = require('./components/qr-code')
// Global Modals
const Modal = require('./components/modals/index').Modal
App.contextTypes = {
// Routes
const {
DEFAULT_ROUTE,
UNLOCK_ROUTE,
SETTINGS_ROUTE,
REVEAL_SEED_ROUTE,
RESTORE_VAULT_ROUTE,
ADD_TOKEN_ROUTE,
NEW_ACCOUNT_ROUTE,
SEND_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
INITIALIZE_ROUTE,
NOTICE_ROUTE,
} = require('./routes')
class App extends Component {
componentWillMount () {
const { currentCurrency, setCurrentCurrencyToUSD } = this.props
if (!currentCurrency) {
setCurrentCurrencyToUSD()
}
}
renderRoutes () {
const exact = true
return (
h(Switch, [
h(Route, { path: INITIALIZE_ROUTE, component: InitializeScreen }),
h(Initialized, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedConfirmation }),
h(Initialized, { path: UNLOCK_ROUTE, exact, component: UnlockPage }),
h(Initialized, { path: SETTINGS_ROUTE, component: Settings }),
h(Initialized, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }),
h(Initialized, { path: NOTICE_ROUTE, exact, component: NoticeScreen }),
h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, component: ConfirmTxScreen }),
h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }),
h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
h(Authenticated, { path: NEW_ACCOUNT_ROUTE, component: CreateAccountPage }),
h(Authenticated, { path: DEFAULT_ROUTE, exact, component: Home }),
])
)
}
render () {
const {
isLoading,
loadingMessage,
network,
isMouseUser,
provider,
frequentRpcList,
currentView,
setMouseUserState,
} = this.props
const isLoadingNetwork = network === 'loading' && currentView.name !== 'config'
const loadMessage = loadingMessage || isLoadingNetwork ?
this.getConnectingLabel() : null
log.debug('Main ui render function')
return (
h('.flex-column.full-height', {
className: classnames({ 'mouse-user-styles': isMouseUser }),
style: {
overflowX: 'hidden',
position: 'relative',
alignItems: 'center',
},
tabIndex: '0',
onClick: () => setMouseUserState(true),
onKeyDown: (e) => {
if (e.keyCode === 9) {
setMouseUserState(false)
}
},
}, [
// global modal
h(Modal, {}, []),
// app bar
this.renderAppBar(),
// sidebar
this.renderSidebar(),
// network dropdown
h(NetworkDropdown, {
provider,
frequentRpcList,
}, []),
h(AccountMenu),
(isLoading || isLoadingNetwork) && h(Loading, {
loadingMessage: loadMessage,
}),
// this.renderLoadingIndicator({ isLoading, isLoadingNetwork, loadMessage }),
// content
this.renderRoutes(),
])
)
}
renderGlobalModal () {
return h(Modal, {
ref: 'modalRef',
}, [
// h(BuyOptions, {}, []),
])
}
renderSidebar () {
return h('div', [
h('style', `
.sidebar-enter {
transition: transform 300ms ease-in-out;
transform: translateX(-100%);
}
.sidebar-enter.sidebar-enter-active {
transition: transform 300ms ease-in-out;
transform: translateX(0%);
}
.sidebar-leave {
transition: transform 200ms ease-out;
transform: translateX(0%);
}
.sidebar-leave.sidebar-leave-active {
transition: transform 200ms ease-out;
transform: translateX(-100%);
}
`),
h(ReactCSSTransitionGroup, {
transitionName: 'sidebar',
transitionEnterTimeout: 300,
transitionLeaveTimeout: 200,
}, [
// A second instance of Walletview is used for non-mobile viewports
this.props.sidebarOpen ? h(WalletView, {
responsiveDisplayClassname: '.sidebar',
style: {},
}) : undefined,
]),
// overlay
// TODO: add onClick for overlay to close sidebar
this.props.sidebarOpen ? h('div.sidebar-overlay', {
style: {},
onClick: () => {
this.props.hideSidebar()
},
}, []) : undefined,
])
}
renderAppBar () {
const {
isUnlocked,
network,
provider,
networkDropdownOpen,
showNetworkDropdown,
hideNetworkDropdown,
isInitialized,
welcomeScreenSeen,
isPopup,
betaUI,
} = this.props
if (window.METAMASK_UI_TYPE === 'notification') {
return null
}
const props = this.props
const {isMascara, isOnboarding} = props
// Do not render header if user is in mascara onboarding
if (isMascara && isOnboarding) {
return null
}
// Do not render header if user is in mascara buy ether
if (isMascara && props.currentView.name === 'buyEth') {
return null
}
return (
h('.full-width', {
style: {},
}, [
(isInitialized || welcomeScreenSeen || isPopup || !betaUI) && h('.app-header.flex-row.flex-space-between', {
className: classnames({
'app-header--initialized': !isOnboarding,
}),
}, [
h('div.app-header-contents', {}, [
h('div.left-menu-wrapper', {
onClick: () => props.history.push(DEFAULT_ROUTE),
}, [
// mini logo
h('img.metafox-icon', {
height: 42,
width: 42,
src: '/images/metamask-fox.svg',
}),
// metamask name
h('.flex-row', [
h('h1', this.context.t('appName')),
h('div.beta-label', this.context.t('beta')),
]),
]),
betaUI && isInitialized && h('div.header__right-actions', [
h('div.network-component-wrapper', {
style: {},
}, [
// Network Indicator
h(NetworkIndicator, {
network,
provider,
disabled: this.props.location.pathname === CONFIRM_TRANSACTION_ROUTE,
onClick: (event) => {
event.preventDefault()
event.stopPropagation()
return networkDropdownOpen === false
? showNetworkDropdown()
: hideNetworkDropdown()
},
}),
]),
isUnlocked && h('div.account-menu__icon', { onClick: this.props.toggleAccountMenu }, [
h(Identicon, {
address: this.props.selectedAddress,
diameter: 32,
}),
]),
]),
]),
]),
!isInitialized && !isPopup && betaUI && h('.alpha-warning__container', {}, [
h('h2', {
className: classnames({
'alpha-warning': welcomeScreenSeen,
'alpha-warning-welcome-screen': !welcomeScreenSeen,
}),
}, 'Please be aware that this version is still under development'),
]),
])
)
}
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
var passwordBox = document.querySelector('input[type=password]')
if (!passwordBox) return
passwordBox.focus()
} else {
// currently active: deactivate
this.props.dispatch(actions.lockMetamask(false))
}
}
getConnectingLabel = function () {
const { provider } = this.props
const providerName = provider.type
let name
if (providerName === 'mainnet') {
name = this.context.t('connectingToMainnet')
} else if (providerName === 'ropsten') {
name = this.context.t('connectingToRopsten')
} else if (providerName === 'kovan') {
name = this.context.t('connectingToRopsten')
} else if (providerName === 'rinkeby') {
name = this.context.t('connectingToRinkeby')
} else {
name = this.context.t('connectingToUnknown')
}
return name
}
getNetworkName () {
const { provider } = this.props
const providerName = provider.type
let name
if (providerName === 'mainnet') {
name = this.context.t('mainnet')
} else if (providerName === 'ropsten') {
name = this.context.t('ropsten')
} else if (providerName === 'kovan') {
name = this.context.t('kovan')
} else if (providerName === 'rinkeby') {
name = this.context.t('rinkeby')
} else {
name = this.context.t('unknownNetwork')
}
return name
}
}
App.propTypes = {
currentCurrency: PropTypes.string,
setCurrentCurrencyToUSD: PropTypes.func,
isLoading: PropTypes.bool,
loadingMessage: PropTypes.string,
network: PropTypes.string,
provider: PropTypes.object,
frequentRpcList: PropTypes.array,
currentView: PropTypes.object,
sidebarOpen: PropTypes.bool,
hideSidebar: PropTypes.func,
isMascara: PropTypes.bool,
isOnboarding: PropTypes.bool,
isUnlocked: PropTypes.bool,
networkDropdownOpen: PropTypes.bool,
showNetworkDropdown: PropTypes.func,
hideNetworkDropdown: PropTypes.func,
history: PropTypes.object,
location: PropTypes.object,
dispatch: PropTypes.func,
toggleAccountMenu: PropTypes.func,
selectedAddress: PropTypes.string,
noActiveNotices: PropTypes.bool,
lostAccounts: PropTypes.array,
isInitialized: PropTypes.bool,
forgottenPassword: PropTypes.bool,
activeAddress: PropTypes.string,
unapprovedTxs: PropTypes.object,
seedWords: PropTypes.string,
unapprovedMsgCount: PropTypes.number,
unapprovedPersonalMsgCount: PropTypes.number,
unapprovedTypedMessagesCount: PropTypes.number,
welcomeScreenSeen: PropTypes.bool,
isPopup: PropTypes.bool,
betaUI: PropTypes.bool,
isMouseUser: PropTypes.bool,
setMouseUserState: PropTypes.func,
t: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(App)
inherits(App, Component)
function App () { Component.call(this) }
function mapStateToProps (state) {
const { appState, metamask } = state
const {
networkDropdownOpen,
sidebarOpen,
isLoading,
loadingMessage,
} = appState
const {
identities,
accounts,
@ -65,17 +424,23 @@ function mapStateToProps (state) {
isInitialized,
noActiveNotices,
seedWords,
} = state.metamask
unapprovedTxs,
lastUnreadNotice,
lostAccounts,
unapprovedMsgCount,
unapprovedPersonalMsgCount,
unapprovedTypedMessagesCount,
} = metamask
const selected = address || Object.keys(accounts)[0]
return {
// state from plugin
networkDropdownOpen: state.appState.networkDropdownOpen,
sidebarOpen: state.appState.sidebarOpen,
isLoading: state.appState.isLoading,
loadingMessage: state.appState.loadingMessage,
noActiveNotices: state.metamask.noActiveNotices,
isInitialized: state.metamask.isInitialized,
networkDropdownOpen,
sidebarOpen,
isLoading,
loadingMessage,
noActiveNotices,
isInitialized,
isUnlocked: state.metamask.isUnlocked,
selectedAddress: state.metamask.selectedAddress,
currentView: state.appState.currentView,
@ -85,14 +450,17 @@ function mapStateToProps (state) {
isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized),
isPopup: state.metamask.isPopup,
seedWords: state.metamask.seedWords,
unapprovedTxs: state.metamask.unapprovedTxs,
unapprovedTxs,
unapprovedMsgs: state.metamask.unapprovedMsgs,
unapprovedMsgCount,
unapprovedPersonalMsgCount,
unapprovedTypedMessagesCount,
menuOpen: state.appState.menuOpen,
network: state.metamask.network,
provider: state.metamask.provider,
forgottenPassword: state.metamask.forgottenPassword,
lastUnreadNotice: state.metamask.lastUnreadNotice,
lostAccounts: state.metamask.lostAccounts,
forgottenPassword: state.appState.forgottenPassword,
lastUnreadNotice,
lostAccounts,
frequentRpcList: state.metamask.frequentRpcList || [],
currentCurrency: state.metamask.currentCurrency,
isMouseUser: state.appState.isMouseUser,
@ -120,479 +488,11 @@ function mapDispatchToProps (dispatch, ownProps) {
}
}
App.prototype.componentWillMount = function () {
if (!this.props.currentCurrency) {
this.props.setCurrentCurrencyToUSD()
}
App.contextTypes = {
t: PropTypes.func,
}
App.prototype.render = function () {
var props = this.props
const {
isLoading,
loadingMessage,
network,
isMouseUser,
setMouseUserState,
} = props
const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config'
const loadMessage = loadingMessage || isLoadingNetwork ?
this.getConnectingLabel() : null
log.debug('Main ui render function')
return (
h('.flex-column.full-height', {
className: classnames({ 'mouse-user-styles': isMouseUser }),
style: {
overflowX: 'hidden',
position: 'relative',
alignItems: 'center',
},
tabIndex: '0',
onClick: () => setMouseUserState(true),
onKeyDown: (e) => {
if (e.keyCode === 9) {
setMouseUserState(false)
}
},
}, [
// global modal
h(Modal, {}, []),
// app bar
this.renderAppBar(),
// sidebar
this.renderSidebar(),
// network dropdown
h(NetworkDropdown, {
provider: this.props.provider,
frequentRpcList: this.props.frequentRpcList,
}, []),
h(AccountMenu),
(isLoading || isLoadingNetwork) && h(Loading, {
loadingMessage: loadMessage,
}),
// this.renderLoadingIndicator({ isLoading, isLoadingNetwork, loadMessage }),
// content
this.renderPrimary(),
])
)
}
App.prototype.renderGlobalModal = function () {
return h(Modal, {
ref: 'modalRef',
}, [
// h(BuyOptions, {}, []),
])
}
App.prototype.renderSidebar = function () {
return h('div', {
}, [
h('style', `
.sidebar-enter {
transition: transform 300ms ease-in-out;
transform: translateX(-100%);
}
.sidebar-enter.sidebar-enter-active {
transition: transform 300ms ease-in-out;
transform: translateX(0%);
}
.sidebar-leave {
transition: transform 200ms ease-out;
transform: translateX(0%);
}
.sidebar-leave.sidebar-leave-active {
transition: transform 200ms ease-out;
transform: translateX(-100%);
}
`),
h(ReactCSSTransitionGroup, {
transitionName: 'sidebar',
transitionEnterTimeout: 300,
transitionLeaveTimeout: 200,
}, [
// A second instance of Walletview is used for non-mobile viewports
this.props.sidebarOpen ? h(WalletView, {
responsiveDisplayClassname: '.sidebar',
style: {},
}) : undefined,
]),
// overlay
// TODO: add onClick for overlay to close sidebar
this.props.sidebarOpen ? h('div.sidebar-overlay', {
style: {},
onClick: () => {
this.props.hideSidebar()
},
}, []) : undefined,
])
}
App.prototype.renderAppBar = function () {
const {
isUnlocked,
network,
provider,
networkDropdownOpen,
showNetworkDropdown,
hideNetworkDropdown,
currentView,
isInitialized,
betaUI,
isPopup,
welcomeScreenSeen,
} = this.props
if (window.METAMASK_UI_TYPE === 'notification') {
return null
}
const props = this.props
const {isMascara, isOnboarding} = props
// Do not render header if user is in mascara onboarding
if (isMascara && isOnboarding) {
return null
}
// Do not render header if user is in mascara buy ether
if (isMascara && props.currentView.name === 'buyEth') {
return null
}
return (
h('.full-width', {
style: {},
}, [
(isInitialized || welcomeScreenSeen || isPopup || !betaUI) && h('.app-header.flex-row.flex-space-between', {
className: classnames({
'app-header--initialized': !isOnboarding,
}),
}, [
h('div.app-header-contents', {}, [
h('div.left-menu-wrapper', {
onClick: () => {
props.dispatch(actions.backToAccountDetail(props.activeAddress))
},
}, [
// mini logo
h('img.metafox-icon', {
height: 42,
width: 42,
src: './images/metamask-fox.svg',
}),
// metamask name
h('.flex-row', [
h('h1', this.context.t('appName')),
h('div.beta-label', this.context.t('beta')),
]),
]),
betaUI && isInitialized && h('div.header__right-actions', [
h('div.network-component-wrapper', {
style: {},
}, [
// Network Indicator
h(NetworkIndicator, {
network,
provider,
disabled: currentView.name === 'confTx',
onClick: (event) => {
event.preventDefault()
event.stopPropagation()
return networkDropdownOpen === false
? showNetworkDropdown()
: hideNetworkDropdown()
},
}),
]),
isUnlocked && h('div.account-menu__icon', { onClick: this.props.toggleAccountMenu }, [
h(Identicon, {
address: this.props.selectedAddress,
diameter: 32,
}),
]),
]),
]),
]),
!isInitialized && !isPopup && betaUI && h('.alpha-warning__container', {}, [
h('h2', {
className: classnames({
'alpha-warning': welcomeScreenSeen,
'alpha-warning-welcome-screen': !welcomeScreenSeen,
}),
}, 'Please be aware that this version is still under development'),
]),
])
)
}
App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork, loadMessage }) {
const { isMascara } = this.props
return isMascara
? null
: h(Loading, {
isLoading: isLoading || isLoadingNetwork,
loadingMessage: loadMessage,
})
}
App.prototype.renderBackButton = function (style, justArrow = false) {
var props = this.props
return (
h('.flex-row', {
key: 'leftArrow',
style: style,
onClick: () => props.dispatch(actions.goBackToInitView()),
}, [
h('i.fa.fa-arrow-left.cursor-pointer'),
justArrow ? null : h('div.cursor-pointer', {
style: {
marginLeft: '3px',
},
onClick: () => props.dispatch(actions.goBackToInitView()),
}, 'BACK'),
])
)
}
App.prototype.renderPrimary = function () {
log.debug('rendering primary')
var props = this.props
const {
isMascara,
isOnboarding,
betaUI,
isRevealingSeedWords,
welcomeScreenSeen,
Qr,
isInitialized,
isUnlocked,
} = props
const isMascaraOnboarding = isMascara && isOnboarding
const isBetaUIOnboarding = betaUI && isOnboarding
if (!welcomeScreenSeen && betaUI && !isInitialized && !isUnlocked) {
return h(WelcomeScreen)
}
if (isMascaraOnboarding || isBetaUIOnboarding) {
return h(MascaraFirstTime)
}
// notices
if (!props.noActiveNotices && !betaUI) {
log.debug('rendering notice screen for unread notices.')
return h(NoticeScreen, {
notice: props.lastUnreadNotice,
key: 'NoticeScreen',
onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
})
} else if (props.lostAccounts && props.lostAccounts.length > 0) {
log.debug('rendering notice screen for lost accounts view.')
return h(NoticeScreen, {
notice: generateLostAccountsNotice(props.lostAccounts),
key: 'LostAccountsNotice',
onConfirm: () => props.dispatch(actions.markAccountsFound()),
})
}
if (props.isInitialized && props.forgottenPassword) {
log.debug('rendering restore vault screen')
return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
} else if (!props.isInitialized && !props.isUnlocked && !isRevealingSeedWords) {
log.debug('rendering menu screen')
return !betaUI
? h(OldUIInitializeMenuScreen, {key: 'menuScreenInit'})
: h(InitializeMenuScreen, {key: 'menuScreenInit'})
}
// show unlock screen
if (!props.isUnlocked) {
return h(MainContainer, {
currentViewName: props.currentView.name,
isUnlocked: props.isUnlocked,
})
}
// show seed words screen
if (props.seedWords) {
log.debug('rendering seed words')
return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
}
// show current view
switch (props.currentView.name) {
case 'accountDetail':
log.debug('rendering main container')
return h(MainContainer, {key: 'account-detail'})
case 'sendTransaction':
log.debug('rendering send tx screen')
// Going to leave this here until we are ready to delete SendTransactionScreen v1
// const SendComponentToRender = checkFeatureToggle('send-v2')
// ? SendTransactionScreen2
// : SendTransactionScreen
return h(SendTransactionScreen2, {key: 'send-transaction'})
case 'sendToken':
log.debug('rendering send token screen')
// Going to leave this here until we are ready to delete SendTransactionScreen v1
// const SendTokenComponentToRender = checkFeatureToggle('send-v2')
// ? SendTransactionScreen2
// : SendTokenScreen
return h(SendTransactionScreen2, {key: 'sendToken'})
case 'newKeychain':
log.debug('rendering new keychain screen')
return h(NewKeyChainScreen, {key: 'new-keychain'})
case 'confTx':
log.debug('rendering confirm tx screen')
return h(ConfirmTxScreen, {key: 'confirm-tx'})
case 'add-token':
log.debug('rendering add-token screen from unlock screen.')
return h(AddTokenScreen, {key: 'add-token'})
case 'config':
log.debug('rendering config screen')
return h(Settings, {key: 'config'})
case 'import-menu':
log.debug('rendering import screen')
return h(Import, {key: 'import-menu'})
case 'new-account-page':
log.debug('rendering new account screen')
return h(NewAccount, {key: 'new-account'})
case 'reveal-seed-conf':
log.debug('rendering reveal seed confirmation screen')
return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})
case 'info':
log.debug('rendering info screen')
return h(Settings, {key: 'info', tab: 'info'})
case 'buyEth':
log.debug('rendering buy ether screen')
return h(BuyView, {key: 'buyEthView'})
case 'onboardingBuyEth':
log.debug('rendering onboarding buy ether screen')
return h(MascaraBuyEtherScreen, {key: 'buyEthView'})
case 'qr':
log.debug('rendering show qr screen')
return h('div', {
style: {
position: 'absolute',
height: '100%',
top: '0px',
left: '0px',
},
}, [
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
onClick: () => props.dispatch(actions.backToAccountDetail(props.activeAddress)),
style: {
marginLeft: '10px',
marginTop: '50px',
},
}),
h('div', {
style: {
position: 'absolute',
left: '44px',
width: '285px',
},
}, [
h(QrView, {key: 'qr', Qr}),
]),
])
default:
log.debug('rendering default, account detail screen')
return h(MainContainer, {key: 'account-detail'})
}
}
App.prototype.toggleMetamaskActive = function () {
if (!this.props.isUnlocked) {
// currently inactive: redirect to password box
var passwordBox = document.querySelector('input[type=password]')
if (!passwordBox) return
passwordBox.focus()
} else {
// currently active: deactivate
this.props.dispatch(actions.lockMetamask(false))
}
}
App.prototype.getConnectingLabel = function () {
const { provider } = this.props
const providerName = provider.type
let name
if (providerName === 'mainnet') {
name = this.context.t('connectingToMainnet')
} else if (providerName === 'ropsten') {
name = this.context.t('connectingToRopsten')
} else if (providerName === 'kovan') {
name = this.context.t('connectingToRopsten')
} else if (providerName === 'rinkeby') {
name = this.context.t('connectingToRinkeby')
} else {
name = this.context.t('connectingToUnknown')
}
return name
}
App.prototype.getNetworkName = function () {
const { provider } = this.props
const providerName = provider.type
let name
if (providerName === 'mainnet') {
name = this.context.t('mainnet')
} else if (providerName === 'ropsten') {
name = this.context.t('ropsten')
} else if (providerName === 'kovan') {
name = this.context.t('kovan')
} else if (providerName === 'rinkeby') {
name = this.context.t('rinkeby')
} else {
name = this.context.t('unknownNetwork')
}
return name
}
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(App)

View File

@ -1,20 +1,31 @@
const inherits = require('util').inherits
const Component = require('react').Component
const PropTypes = require('prop-types')
const connect = require('react-redux').connect
const { compose } = require('recompose')
const { withRouter } = require('react-router-dom')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const actions = require('../../actions')
const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
const Identicon = require('../identicon')
const { formatBalance } = require('../../util')
const {
SETTINGS_ROUTE,
INFO_ROUTE,
NEW_ACCOUNT_ROUTE,
IMPORT_ACCOUNT_ROUTE,
DEFAULT_ROUTE,
} = require('../../routes')
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(AccountMenu)
AccountMenu.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountMenu)
inherits(AccountMenu, Component)
function AccountMenu () { Component.call(this) }
@ -25,7 +36,6 @@ function mapStateToProps (state) {
keyrings: state.metamask.keyrings,
identities: state.metamask.identities,
accounts: state.metamask.accounts,
}
}
@ -48,11 +58,6 @@ function mapDispatchToProps (dispatch) {
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
showNewAccountPage: (formToSelect) => {
dispatch(actions.showNewAccountPage(formToSelect))
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
showInfoPage: () => {
dispatch(actions.showInfoPage())
dispatch(actions.hideSidebar())
@ -65,10 +70,8 @@ AccountMenu.prototype.render = function () {
const {
isAccountMenuOpen,
toggleAccountMenu,
showNewAccountPage,
lockMetamask,
showConfigPage,
showInfoPage,
history,
} = this.props
return h(Menu, { className: 'account-menu', isShowing: isAccountMenuOpen }, [
@ -78,30 +81,45 @@ AccountMenu.prototype.render = function () {
}, [
this.context.t('myAccounts'),
h('button.account-menu__logout-button', {
onClick: lockMetamask,
onClick: () => {
lockMetamask()
history.push(DEFAULT_ROUTE)
},
}, this.context.t('logout')),
]),
h(Divider),
h('div.account-menu__accounts', this.renderAccounts()),
h(Divider),
h(Item, {
onClick: () => showNewAccountPage('CREATE'),
onClick: () => {
toggleAccountMenu()
history.push(NEW_ACCOUNT_ROUTE)
},
icon: h('img.account-menu__item-icon', { src: 'images/plus-btn-white.svg' }),
text: this.context.t('createAccount'),
}),
h(Item, {
onClick: () => showNewAccountPage('IMPORT'),
onClick: () => {
toggleAccountMenu()
history.push(IMPORT_ACCOUNT_ROUTE)
},
icon: h('img.account-menu__item-icon', { src: 'images/import-account.svg' }),
text: this.context.t('importAccount'),
}),
h(Divider),
h(Item, {
onClick: showInfoPage,
onClick: () => {
toggleAccountMenu()
history.push(INFO_ROUTE)
},
icon: h('img', { src: 'images/mm-info-icon.svg' }),
text: this.context.t('infoHelp'),
}),
h(Item, {
onClick: showConfigPage,
onClick: () => {
toggleAccountMenu()
history.push(SETTINGS_ROUTE)
},
icon: h('img.account-menu__item-icon', { src: 'images/settings.svg' }),
text: this.context.t('settings'),
}),

View File

@ -32,10 +32,10 @@ EnsInput.prototype.render = function () {
const network = this.props.network
const networkHasEnsSupport = getNetworkEnsSupport(network)
if (!networkHasEnsSupport) return
props.onChange(recipient)
if (!networkHasEnsSupport) return
if (recipient.match(ensRE) === null) {
return this.setState({
loadingEns: false,

View File

@ -7,8 +7,8 @@ const connect = require('react-redux').connect
const R = require('ramda')
const Fuse = require('fuse.js')
const contractMap = require('eth-contract-metadata')
const TokenBalance = require('./components/token-balance')
const Identicon = require('./components/identicon')
const TokenBalance = require('../../components/token-balance')
const Identicon = require('../../components/identicon')
const contractList = Object.entries(contractMap)
.map(([ _, tokenData]) => tokenData)
.filter(tokenData => Boolean(tokenData.erc20))
@ -24,9 +24,10 @@ const fuse = new Fuse(contractList, {
{ name: 'symbol', weight: 0.5 },
],
})
const actions = require('./actions')
const actions = require('../../actions')
const ethUtil = require('ethereumjs-util')
const { tokenInfoGetter } = require('./token-util')
const { tokenInfoGetter } = require('../../token-util')
const { DEFAULT_ROUTE } = require('../../routes')
const emptyAddr = '0x0000000000000000000000000000000000000000'
@ -47,7 +48,6 @@ function mapStateToProps (state) {
function mapDispatchToProps (dispatch) {
return {
goHome: () => dispatch(actions.goHome()),
addTokens: tokens => dispatch(actions.addTokens(tokens)),
}
}
@ -56,6 +56,7 @@ inherits(AddTokenScreen, Component)
function AddTokenScreen () {
this.state = {
isShowingConfirmation: false,
isShowingInfoBox: true,
customAddress: '',
customSymbol: '',
customDecimals: '',
@ -295,7 +296,7 @@ AddTokenScreen.prototype.renderConfirmation = function () {
selectedTokens,
} = this.state
const { addTokens, goHome } = this.props
const { addTokens, history } = this.props
const customToken = {
address,
@ -310,9 +311,6 @@ AddTokenScreen.prototype.renderConfirmation = function () {
return (
h('div.add-token', [
h('div.add-token__wrapper', [
h('div.add-token__title-container.add-token__confirmation-title', [
h('div.add-token__description', this.context.t('likeToAddTokens')),
]),
h('div.add-token__content-container.add-token__confirmation-content', [
h('div.add-token__description.add-token__confirmation-description', this.context.t('balances')),
h('div.add-token__confirmation-token-list',
@ -335,7 +333,7 @@ AddTokenScreen.prototype.renderConfirmation = function () {
onClick: () => this.setState({ isShowingConfirmation: false }),
}, this.context.t('back')),
h('button.btn-primary--lg', {
onClick: () => addTokens(tokens).then(goHome),
onClick: () => addTokens(tokens).then(() => history.push(DEFAULT_ROUTE)),
}, this.context.t('addTokens')),
]),
])
@ -347,18 +345,23 @@ AddTokenScreen.prototype.displayTab = function (selectedTab) {
}
AddTokenScreen.prototype.renderTabs = function () {
const { displayedTab, errors } = this.state
const { isShowingInfoBox, displayedTab, errors } = this.state
return displayedTab === 'CUSTOM_TOKEN'
? this.renderCustomForm()
: h('div', [
h('div.add-token__wrapper', [
h('div.add-token__content-container', [
h('div.add-token__info-box', [
h('div.add-token__info-box__close'),
isShowingInfoBox && h('div.add-token__info-box', [
h('div.add-token__info-box__close', {
onClick: () => this.setState({ isShowingInfoBox: false }),
}),
h('div.add-token__info-box__title', this.context.t('whatsThis')),
h('div.add-token__info-box__copy', this.context.t('keepTrackTokens')),
h('div.add-token__info-box__copy--blue', this.context.t('learnMore')),
h('a.add-token__info-box__copy--blue', {
href: 'http://metamask.helpscoutdocs.com/article/16-managing-erc20-tokens',
target: '_blank',
}, this.context.t('learnMore')),
]),
h('div.add-token__input-container', [
h('input.add-token__input', {
@ -379,23 +382,24 @@ AddTokenScreen.prototype.render = function () {
isShowingConfirmation,
displayedTab,
} = this.state
const { goHome } = this.props
const { history } = this.props
return h('div.add-token', [
h('div.add-token__header', [
h('div.add-token__header__cancel', {
onClick: () => goHome(),
onClick: () => history.push(DEFAULT_ROUTE),
}, [
h('i.fa.fa-angle-left.fa-lg'),
h('span', this.context.t('cancel')),
]),
h('div.add-token__header__title', this.context.t('addTokens')),
isShowingConfirmation && h('div.add-token__header__subtitle', this.context.t('likeToAddTokens')),
!isShowingConfirmation && h('div.add-token__header__tabs', [
h('div.add-token__header__tabs__tab', {
className: classnames('add-token__header__tabs__tab', {
'add-token__header__tabs__selected': displayedTab === 'SEARCH',
'add-token__header__tabs__unselected cursor-pointer': displayedTab !== 'SEARCH',
'add-token__header__tabs__unselected': displayedTab !== 'SEARCH',
}),
onClick: () => this.displayTab('SEARCH'),
}, this.context.t('search')),
@ -403,21 +407,21 @@ AddTokenScreen.prototype.render = function () {
h('div.add-token__header__tabs__tab', {
className: classnames('add-token__header__tabs__tab', {
'add-token__header__tabs__selected': displayedTab === 'CUSTOM_TOKEN',
'add-token__header__tabs__unselected cursor-pointer': displayedTab !== 'CUSTOM_TOKEN',
'add-token__header__tabs__unselected': displayedTab !== 'CUSTOM_TOKEN',
}),
onClick: () => this.displayTab('CUSTOM_TOKEN'),
}, this.context.t('customToken')),
]),
]),
//
isShowingConfirmation
? this.renderConfirmation()
: this.renderTabs(),
!isShowingConfirmation && h('div.add-token__buttons', [
h('button.btn-secondary--lg.add-token__cancel-button', {
onClick: goHome,
onClick: () => history.push(DEFAULT_ROUTE),
}, this.context.t('cancel')),
h('button.btn-primary--lg.add-token__confirm-button', {
onClick: this.onNext,

View File

@ -0,0 +1,34 @@
const { connect } = require('react-redux')
const PropTypes = require('prop-types')
const { Redirect } = require('react-router-dom')
const h = require('react-hyperscript')
const MetamaskRoute = require('./metamask-route')
const { UNLOCK_ROUTE, INITIALIZE_ROUTE } = require('../../routes')
const Authenticated = props => {
const { isUnlocked, isInitialized } = props
switch (true) {
case isUnlocked && isInitialized:
return h(MetamaskRoute, { ...props })
case !isInitialized:
return h(Redirect, { to: { pathname: INITIALIZE_ROUTE } })
default:
return h(Redirect, { to: { pathname: UNLOCK_ROUTE } })
}
}
Authenticated.propTypes = {
isUnlocked: PropTypes.bool,
isInitialized: PropTypes.bool,
}
const mapStateToProps = state => {
const { metamask: { isUnlocked, isInitialized } } = state
return {
isUnlocked,
isInitialized,
}
}
module.exports = connect(mapStateToProps)(Authenticated)

View File

@ -1,11 +1,12 @@
const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const connect = require('react-redux').connect
const actions = require('../../actions')
const actions = require('../../../../actions')
const FileInput = require('react-simple-file-input').default
const { DEFAULT_ROUTE } = require('../../../../routes')
const HELP_LINK = 'https://support.metamask.io/kb/article/7-importing-accounts'
class JsonImportSubview extends Component {
@ -51,7 +52,7 @@ class JsonImportSubview extends Component {
h('div.new-account-create-form__buttons', {}, [
h('button.btn-secondary.new-account-create-form__button', {
onClick: () => this.props.goHome(),
onClick: () => this.props.history.push(DEFAULT_ROUTE),
}, [
this.context.t('cancel'),
]),
@ -112,6 +113,7 @@ JsonImportSubview.propTypes = {
goHome: PropTypes.func,
displayWarning: PropTypes.func,
importNewJsonAccount: PropTypes.func,
history: PropTypes.object,
t: PropTypes.func,
}
@ -133,5 +135,7 @@ JsonImportSubview.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(JsonImportSubview)
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(JsonImportSubview)

View File

@ -1,15 +1,21 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const PropTypes = require('prop-types')
const connect = require('react-redux').connect
const actions = require('../../actions')
const actions = require('../../../../actions')
const { DEFAULT_ROUTE } = require('../../../../routes')
PrivateKeyImportView.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(PrivateKeyImportView)
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(PrivateKeyImportView)
function mapStateToProps (state) {
@ -20,9 +26,8 @@ function mapStateToProps (state) {
function mapDispatchToProps (dispatch) {
return {
goHome: () => dispatch(actions.goHome()),
importNewAccount: (strategy, [ privateKey ]) => {
dispatch(actions.importNewAccount(strategy, [ privateKey ]))
return dispatch(actions.importNewAccount(strategy, [ privateKey ]))
},
displayWarning: () => dispatch(actions.displayWarning(null)),
}
@ -30,11 +35,12 @@ function mapDispatchToProps (dispatch) {
inherits(PrivateKeyImportView, Component)
function PrivateKeyImportView () {
this.createKeyringOnEnter = this.createKeyringOnEnter.bind(this)
Component.call(this)
}
PrivateKeyImportView.prototype.render = function () {
const { error, goHome } = this.props
const { error } = this.props
return (
h('div.new-account-import-form__private-key', [
@ -46,7 +52,7 @@ PrivateKeyImportView.prototype.render = function () {
h('input.new-account-import-form__input-password', {
type: 'password',
id: 'private-key-box',
onKeyPress: () => this.createKeyringOnEnter(),
onKeyPress: e => this.createKeyringOnEnter(e),
}),
]),
@ -54,7 +60,7 @@ PrivateKeyImportView.prototype.render = function () {
h('div.new-account-import-form__buttons', {}, [
h('button.btn-secondary--lg.new-account-create-form__button', {
onClick: () => goHome(),
onClick: () => this.props.history.push(DEFAULT_ROUTE),
}, [
this.context.t('cancel'),
]),
@ -82,6 +88,8 @@ PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) {
PrivateKeyImportView.prototype.createNewKeychain = function () {
const input = document.getElementById('private-key-box')
const privateKey = input.value
const { importNewAccount, history } = this.props
this.props.importNewAccount('Private Key', [ privateKey ])
importNewAccount('Private Key', [ privateKey ])
.then(() => history.push(DEFAULT_ROUTE))
}

View File

@ -0,0 +1,81 @@
const Component = require('react').Component
const { Switch, Route, matchPath } = require('react-router-dom')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../../actions')
const { getCurrentViewContext } = require('../../../selectors')
const classnames = require('classnames')
const NewAccountCreateForm = require('./new-account')
const NewAccountImportForm = require('./import-account')
const { NEW_ACCOUNT_ROUTE, IMPORT_ACCOUNT_ROUTE } = require('../../../routes')
class CreateAccountPage extends Component {
renderTabs () {
const { history, location } = this.props
return h('div.new-account__tabs', [
h('div.new-account__tabs__tab', {
className: classnames('new-account__tabs__tab', {
'new-account__tabs__selected': matchPath(location.pathname, {
path: NEW_ACCOUNT_ROUTE, exact: true,
}),
}),
onClick: () => history.push(NEW_ACCOUNT_ROUTE),
}, 'Create'),
h('div.new-account__tabs__tab', {
className: classnames('new-account__tabs__tab', {
'new-account__tabs__selected': matchPath(location.pathname, {
path: IMPORT_ACCOUNT_ROUTE, exact: true,
}),
}),
onClick: () => history.push(IMPORT_ACCOUNT_ROUTE),
}, 'Import'),
])
}
render () {
return h('div.new-account', {}, [
h('div.new-account__header', [
h('div.new-account__title', 'New Account'),
this.renderTabs(),
]),
h('div.new-account__form', [
h(Switch, [
h(Route, {
exact: true,
path: NEW_ACCOUNT_ROUTE,
component: NewAccountCreateForm,
}),
h(Route, {
exact: true,
path: IMPORT_ACCOUNT_ROUTE,
component: NewAccountImportForm,
}),
]),
]),
])
}
}
CreateAccountPage.propTypes = {
location: PropTypes.object,
history: PropTypes.object,
}
const mapStateToProps = state => ({
displayedForm: getCurrentViewContext(state),
})
const mapDispatchToProps = dispatch => ({
displayForm: form => dispatch(actions.setNewAccountForm(form)),
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
showExportPrivateKeyModal: () => {
dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
},
hideModal: () => dispatch(actions.hideModal()),
saveAccountLabel: (address, label) => dispatch(actions.saveAccountLabel(address, label)),
})
module.exports = connect(mapStateToProps, mapDispatchToProps)(CreateAccountPage)

View File

@ -2,7 +2,8 @@ const { Component } = require('react')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../actions')
const actions = require('../../../actions')
const { DEFAULT_ROUTE } = require('../../../routes')
class NewAccountCreateForm extends Component {
constructor (props, context) {
@ -19,7 +20,7 @@ class NewAccountCreateForm extends Component {
render () {
const { newAccountName, defaultAccountName } = this.state
const { history, createAccount } = this.props
return h('div.new-account-create-form', [
@ -38,13 +39,16 @@ class NewAccountCreateForm extends Component {
h('div.new-account-create-form__buttons', {}, [
h('button.btn-secondary--lg.new-account-create-form__button', {
onClick: () => this.props.goHome(),
onClick: () => history.push(DEFAULT_ROUTE),
}, [
this.context.t('cancel'),
]),
h('button.btn-primary--lg.new-account-create-form__button', {
onClick: () => this.props.createAccount(newAccountName || defaultAccountName),
onClick: () => {
createAccount(newAccountName || defaultAccountName)
.then(() => history.push(DEFAULT_ROUTE))
},
}, [
this.context.t('create'),
]),
@ -59,8 +63,8 @@ NewAccountCreateForm.propTypes = {
hideModal: PropTypes.func,
showImportPage: PropTypes.func,
createAccount: PropTypes.func,
goHome: PropTypes.func,
numberOfExistingAccounts: PropTypes.number,
history: PropTypes.object,
t: PropTypes.func,
}
@ -77,23 +81,17 @@ const mapStateToProps = state => {
const mapDispatchToProps = dispatch => {
return {
toCoinbase: (address) => {
dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
},
hideModal: () => {
dispatch(actions.hideModal())
},
createAccount: (newAccountName) => {
dispatch(actions.addNewAccount())
.then((newAccountAddress) => {
toCoinbase: address => dispatch(actions.buyEth({ network: '1', address, amount: 0 })),
hideModal: () => dispatch(actions.hideModal()),
createAccount: newAccountName => {
return dispatch(actions.addNewAccount())
.then(newAccountAddress => {
if (newAccountName) {
dispatch(actions.saveAccountLabel(newAccountAddress, newAccountName))
}
dispatch(actions.goHome())
})
},
showImportPage: () => dispatch(actions.showImportPage()),
goHome: () => dispatch(actions.goHome()),
}
}

View File

@ -0,0 +1,332 @@
const { Component } = require('react')
const PropTypes = require('prop-types')
const connect = require('../../metamask-connect')
const { Redirect, withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const h = require('react-hyperscript')
const actions = require('../../actions')
// init
const NewKeyChainScreen = require('../../new-keychain')
// mascara
const MascaraBuyEtherScreen = require('../../../../mascara/src/app/first-time/buy-ether-screen').default
// accounts
const MainContainer = require('../../main-container')
// other views
const BuyView = require('../../components/buy-button-subview')
const QrView = require('../../components/qr-code')
// Routes
const {
REVEAL_SEED_ROUTE,
RESTORE_VAULT_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
NOTICE_ROUTE,
} = require('../../routes')
class Home extends Component {
componentDidMount () {
const {
history,
unapprovedTxs = {},
unapprovedMsgCount = 0,
unapprovedPersonalMsgCount = 0,
unapprovedTypedMessagesCount = 0,
} = this.props
// unapprovedTxs and unapproved messages
if (Object.keys(unapprovedTxs).length ||
unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) {
history.push(CONFIRM_TRANSACTION_ROUTE)
}
}
render () {
log.debug('rendering primary')
const {
noActiveNotices,
lostAccounts,
forgottenPassword,
currentView,
activeAddress,
seedWords,
} = this.props
// notices
if (!noActiveNotices || (lostAccounts && lostAccounts.length > 0)) {
return h(Redirect, {
to: {
pathname: NOTICE_ROUTE,
},
})
}
// seed words
if (seedWords) {
log.debug('rendering seed words')
return h(Redirect, {
to: {
pathname: REVEAL_SEED_ROUTE,
},
})
}
if (forgottenPassword) {
log.debug('rendering restore vault screen')
return h(Redirect, {
to: {
pathname: RESTORE_VAULT_ROUTE,
},
})
}
// if (!props.noActiveNotices) {
// log.debug('rendering notice screen for unread notices.')
// return h(NoticeScreen, {
// notice: props.lastUnreadNotice,
// key: 'NoticeScreen',
// onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
// })
// } else if (props.lostAccounts && props.lostAccounts.length > 0) {
// log.debug('rendering notice screen for lost accounts view.')
// return h(NoticeScreen, {
// notice: generateLostAccountsNotice(props.lostAccounts),
// key: 'LostAccountsNotice',
// onConfirm: () => props.dispatch(actions.markAccountsFound()),
// })
// }
// if (props.seedWords) {
// log.debug('rendering seed words')
// return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
// }
// show initialize screen
// if (!isInitialized || forgottenPassword) {
// // show current view
// log.debug('rendering an initialize screen')
// // switch (props.currentView.name) {
// // case 'restoreVault':
// // log.debug('rendering restore vault screen')
// // return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
// // default:
// // log.debug('rendering menu screen')
// // return h(InitializeScreen, {key: 'menuScreenInit'})
// // }
// }
// // show unlock screen
// if (!props.isUnlocked) {
// return h(MainContainer, {
// currentViewName: props.currentView.name,
// isUnlocked: props.isUnlocked,
// })
// }
// show current view
switch (currentView.name) {
case 'accountDetail':
log.debug('rendering main container')
return h(MainContainer, {key: 'account-detail'})
// case 'sendTransaction':
// log.debug('rendering send tx screen')
// // Going to leave this here until we are ready to delete SendTransactionScreen v1
// // const SendComponentToRender = checkFeatureToggle('send-v2')
// // ? SendTransactionScreen2
// // : SendTransactionScreen
// return h(SendTransactionScreen2, {key: 'send-transaction'})
// case 'sendToken':
// log.debug('rendering send token screen')
// // Going to leave this here until we are ready to delete SendTransactionScreen v1
// // const SendTokenComponentToRender = checkFeatureToggle('send-v2')
// // ? SendTransactionScreen2
// // : SendTokenScreen
// return h(SendTransactionScreen2, {key: 'sendToken'})
case 'newKeychain':
log.debug('rendering new keychain screen')
return h(NewKeyChainScreen, {key: 'new-keychain'})
// case 'confTx':
// log.debug('rendering confirm tx screen')
// return h(Redirect, {
// to: {
// pathname: CONFIRM_TRANSACTION_ROUTE,
// },
// })
// return h(ConfirmTxScreen, {key: 'confirm-tx'})
// case 'add-token':
// log.debug('rendering add-token screen from unlock screen.')
// return h(AddTokenScreen, {key: 'add-token'})
// case 'config':
// log.debug('rendering config screen')
// return h(Settings, {key: 'config'})
// case 'import-menu':
// log.debug('rendering import screen')
// return h(Import, {key: 'import-menu'})
// case 'reveal-seed-conf':
// log.debug('rendering reveal seed confirmation screen')
// return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})
// case 'info':
// log.debug('rendering info screen')
// return h(Settings, {key: 'info', tab: 'info'})
case 'buyEth':
log.debug('rendering buy ether screen')
return h(BuyView, {key: 'buyEthView'})
case 'onboardingBuyEth':
log.debug('rendering onboarding buy ether screen')
return h(MascaraBuyEtherScreen, {key: 'buyEthView'})
case 'qr':
log.debug('rendering show qr screen')
return h('div', {
style: {
position: 'absolute',
height: '100%',
top: '0px',
left: '0px',
},
}, [
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
onClick: () => this.props.dispatch(actions.backToAccountDetail(activeAddress)),
style: {
marginLeft: '10px',
marginTop: '50px',
},
}),
h('div', {
style: {
position: 'absolute',
left: '44px',
width: '285px',
},
}, [
h(QrView, {key: 'qr'}),
]),
])
default:
log.debug('rendering default, account detail screen')
return h(MainContainer, {key: 'account-detail'})
}
}
}
Home.propTypes = {
currentCurrency: PropTypes.string,
isLoading: PropTypes.bool,
loadingMessage: PropTypes.string,
network: PropTypes.string,
provider: PropTypes.object,
frequentRpcList: PropTypes.array,
currentView: PropTypes.object,
sidebarOpen: PropTypes.bool,
isMascara: PropTypes.bool,
isOnboarding: PropTypes.bool,
isUnlocked: PropTypes.bool,
networkDropdownOpen: PropTypes.bool,
history: PropTypes.object,
dispatch: PropTypes.func,
selectedAddress: PropTypes.string,
noActiveNotices: PropTypes.bool,
lostAccounts: PropTypes.array,
isInitialized: PropTypes.bool,
forgottenPassword: PropTypes.bool,
activeAddress: PropTypes.string,
unapprovedTxs: PropTypes.object,
seedWords: PropTypes.string,
unapprovedMsgCount: PropTypes.number,
unapprovedPersonalMsgCount: PropTypes.number,
unapprovedTypedMessagesCount: PropTypes.number,
welcomeScreenSeen: PropTypes.bool,
isPopup: PropTypes.bool,
isMouseUser: PropTypes.bool,
t: PropTypes.func,
}
function mapStateToProps (state) {
const { appState, metamask } = state
const {
networkDropdownOpen,
sidebarOpen,
isLoading,
loadingMessage,
} = appState
const {
accounts,
address,
isInitialized,
noActiveNotices,
seedWords,
unapprovedTxs,
lastUnreadNotice,
lostAccounts,
unapprovedMsgCount,
unapprovedPersonalMsgCount,
unapprovedTypedMessagesCount,
} = metamask
const selected = address || Object.keys(accounts)[0]
return {
// state from plugin
networkDropdownOpen,
sidebarOpen,
isLoading,
loadingMessage,
noActiveNotices,
isInitialized,
isUnlocked: state.metamask.isUnlocked,
selectedAddress: state.metamask.selectedAddress,
currentView: state.appState.currentView,
activeAddress: state.appState.activeAddress,
transForward: state.appState.transForward,
isMascara: state.metamask.isMascara,
isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized),
isPopup: state.metamask.isPopup,
seedWords: state.metamask.seedWords,
unapprovedTxs,
unapprovedMsgs: state.metamask.unapprovedMsgs,
unapprovedMsgCount,
unapprovedPersonalMsgCount,
unapprovedTypedMessagesCount,
menuOpen: state.appState.menuOpen,
network: state.metamask.network,
provider: state.metamask.provider,
forgottenPassword: state.appState.forgottenPassword,
lastUnreadNotice,
lostAccounts,
frequentRpcList: state.metamask.frequentRpcList || [],
currentCurrency: state.metamask.currentCurrency,
isMouseUser: state.appState.isMouseUser,
isRevealingSeedWords: state.metamask.isRevealingSeedWords,
Qr: state.appState.Qr,
welcomeScreenSeen: state.metamask.welcomeScreenSeen,
// state needed to get account dropdown temporarily rendering from app bar
selected,
}
}
module.exports = compose(
withRouter,
connect(mapStateToProps)
)(Home)

View File

@ -0,0 +1,25 @@
const { connect } = require('react-redux')
const PropTypes = require('prop-types')
const { Redirect } = require('react-router-dom')
const h = require('react-hyperscript')
const { INITIALIZE_ROUTE } = require('../../routes')
const MetamaskRoute = require('./metamask-route')
const Initialized = props => {
return props.isInitialized
? h(MetamaskRoute, { ...props })
: h(Redirect, { to: { pathname: INITIALIZE_ROUTE } })
}
Initialized.propTypes = {
isInitialized: PropTypes.bool,
}
const mapStateToProps = state => {
const { metamask: { isInitialized } } = state
return {
isInitialized,
}
}
module.exports = connect(mapStateToProps)(Initialized)

View File

@ -0,0 +1,177 @@
const { withRouter } = require('react-router-dom')
const PropTypes = require('prop-types')
const { compose } = require('recompose')
const PersistentForm = require('../../../../lib/persistent-form')
const connect = require('../../../metamask-connect')
const h = require('react-hyperscript')
const { createNewVaultAndRestore, unMarkPasswordForgotten } = require('../../../actions')
const { DEFAULT_ROUTE } = require('../../../routes')
class RestoreVaultPage extends PersistentForm {
constructor (props) {
super(props)
this.state = {
error: null,
}
}
createOnEnter (event) {
if (event.key === 'Enter') {
this.createNewVaultAndRestore()
}
}
cancel () {
this.props.unMarkPasswordForgotten()
.then(this.props.history.push(DEFAULT_ROUTE))
}
createNewVaultAndRestore () {
this.setState({ error: null })
// check password
var passwordBox = document.getElementById('password-box')
var password = passwordBox.value
var passwordConfirmBox = document.getElementById('password-box-confirm')
var passwordConfirm = passwordConfirmBox.value
if (password.length < 8) {
this.setState({ error: 'Password not long enough' })
return
}
if (password !== passwordConfirm) {
this.setState({ error: 'Passwords don\'t match' })
return
}
// check seed
var seedBox = document.querySelector('textarea.twelve-word-phrase')
var seed = seedBox.value.trim()
if (seed.split(' ').length !== 12) {
this.setState({ error: 'Seed phrases are 12 words long' })
return
}
// submit
this.props.createNewVaultAndRestore(password, seed)
.then(() => this.props.history.push(DEFAULT_ROUTE))
.catch(({ message }) => {
this.setState({ error: message })
log.error(message)
})
}
render () {
const { error } = this.state
this.persistentFormParentId = 'restore-vault-form'
return (
h('.initialize-screen.flex-column.flex-center.flex-grow', [
h('h3.flex-center.text-transform-uppercase', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
marginBottom: 24,
width: '100%',
fontSize: '20px',
padding: 6,
},
}, [
this.props.t('restoreVault'),
]),
// wallet seed entry
h('h3', 'Wallet Seed'),
h('textarea.twelve-word-phrase.letter-spacey', {
dataset: {
persistentFormId: 'wallet-seed',
},
placeholder: this.props.t('secretPhrase'),
}),
// password
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box',
placeholder: this.props.t('newPassword8Chars'),
dataset: {
persistentFormId: 'password',
},
style: {
width: 260,
marginTop: 12,
},
}),
// confirm password
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box-confirm',
placeholder: this.props.t('confirmPassword'),
onKeyPress: this.createOnEnter.bind(this),
dataset: {
persistentFormId: 'password-confirmation',
},
style: {
width: 260,
marginTop: 16,
},
}),
error && (
h('span.error.in-progress-notification', error)
),
// submit
h('.flex-row.flex-space-between', {
style: {
marginTop: 30,
width: '50%',
},
}, [
// cancel
h('button.primary', {
onClick: () => this.cancel(),
}, this.props.t('cancel')),
// submit
h('button.primary', {
onClick: this.createNewVaultAndRestore.bind(this),
}, this.props.t('ok')),
]),
])
)
}
}
RestoreVaultPage.propTypes = {
history: PropTypes.object,
}
const mapStateToProps = state => {
const { appState: { warning, forgottenPassword } } = state
return {
warning,
forgottenPassword,
}
}
const mapDispatchToProps = dispatch => {
return {
createNewVaultAndRestore: (password, seed) => {
return dispatch(createNewVaultAndRestore(password, seed))
},
unMarkPasswordForgotten: () => dispatch(unMarkPasswordForgotten()),
}
}
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(RestoreVaultPage)

View File

@ -0,0 +1,195 @@
const { Component } = require('react')
const { connect } = require('react-redux')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const { exportAsFile } = require('../../../util')
const { requestRevealSeed, confirmSeedWords } = require('../../../actions')
const { DEFAULT_ROUTE } = require('../../../routes')
class RevealSeedPage extends Component {
componentDidMount () {
const passwordBox = document.getElementById('password-box')
if (passwordBox) {
passwordBox.focus()
}
}
checkConfirmation (event) {
if (event.key === 'Enter') {
event.preventDefault()
this.revealSeedWords()
}
}
revealSeedWords () {
const password = document.getElementById('password-box').value
this.props.requestRevealSeed(password)
}
renderSeed () {
const { seedWords, confirmSeedWords, history } = this.props
return (
h('.initialize-screen.flex-column.flex-center.flex-grow', [
h('h3.flex-center.text-transform-uppercase', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
marginTop: 36,
marginBottom: 8,
width: '100%',
fontSize: '20px',
padding: 6,
},
}, [
'Vault Created',
]),
h('div', {
style: {
fontSize: '1em',
marginTop: '10px',
textAlign: 'center',
},
}, [
h('span.error', 'These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret.'),
]),
h('textarea.twelve-word-phrase', {
readOnly: true,
value: seedWords,
}),
h('button.primary', {
onClick: () => confirmSeedWords().then(() => history.push(DEFAULT_ROUTE)),
style: {
margin: '24px',
fontSize: '0.9em',
marginBottom: '10px',
},
}, 'I\'ve copied it somewhere safe'),
h('button.primary', {
onClick: () => exportAsFile(`MetaMask Seed Words`, seedWords),
style: {
margin: '10px',
fontSize: '0.9em',
},
}, 'Save Seed Words As File'),
])
)
}
renderConfirmation () {
const { history, warning, inProgress } = this.props
return (
h('.initialize-screen.flex-column.flex-center.flex-grow', {
style: { maxWidth: '420px' },
}, [
h('h3.flex-center.text-transform-uppercase', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
marginBottom: 24,
width: '100%',
fontSize: '20px',
padding: 6,
},
}, [
'Reveal Seed Words',
]),
h('.div', {
style: {
display: 'flex',
flexDirection: 'column',
padding: '20px',
justifyContent: 'center',
},
}, [
h('h4', 'Do not recover your seed words in a public place! These words can be used to steal all your accounts.'),
// confirmation
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box',
placeholder: 'Enter your password to confirm',
onKeyPress: this.checkConfirmation.bind(this),
style: {
width: 260,
marginTop: '12px',
},
}),
h('.flex-row.flex-start', {
style: {
marginTop: 30,
width: '50%',
},
}, [
// cancel
h('button.primary', {
onClick: () => history.push(DEFAULT_ROUTE),
}, 'CANCEL'),
// submit
h('button.primary', {
style: { marginLeft: '10px' },
onClick: this.revealSeedWords.bind(this),
}, 'OK'),
]),
warning && (
h('span.error', {
style: {
margin: '20px',
},
}, warning.split('-'))
),
inProgress && (
h('span.in-progress-notification', 'Generating Seed...')
),
]),
])
)
}
render () {
return this.props.seedWords
? this.renderSeed()
: this.renderConfirmation()
}
}
RevealSeedPage.propTypes = {
requestRevealSeed: PropTypes.func,
confirmSeedWords: PropTypes.func,
seedWords: PropTypes.string,
inProgress: PropTypes.bool,
history: PropTypes.object,
warning: PropTypes.string,
}
const mapStateToProps = state => {
const { appState: { warning }, metamask: { seedWords } } = state
return {
warning,
seedWords,
}
}
const mapDispatchToProps = dispatch => {
return {
requestRevealSeed: password => dispatch(requestRevealSeed(password)),
confirmSeedWords: () => dispatch(confirmSeedWords()),
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(RevealSeedPage)

View File

@ -0,0 +1,28 @@
const { connect } = require('react-redux')
const PropTypes = require('prop-types')
const { Route } = require('react-router-dom')
const h = require('react-hyperscript')
const MetamaskRoute = ({ component, mascaraComponent, isMascara, ...props }) => {
return (
h(Route, {
...props,
component: isMascara && mascaraComponent ? mascaraComponent : component,
})
)
}
MetamaskRoute.propTypes = {
component: PropTypes.func,
mascaraComponent: PropTypes.func,
isMascara: PropTypes.bool,
}
const mapStateToProps = state => {
const { metamask: { isMascara } } = state
return {
isMascara,
}
}
module.exports = connect(mapStateToProps)(MetamaskRoute)

View File

@ -0,0 +1,203 @@
const { Component } = require('react')
const h = require('react-hyperscript')
const { connect } = require('react-redux')
const PropTypes = require('prop-types')
const ReactMarkdown = require('react-markdown')
const linker = require('extension-link-enabler')
const generateLostAccountsNotice = require('../../../lib/lost-accounts-notice')
const findDOMNode = require('react-dom').findDOMNode
const actions = require('../../actions')
const { DEFAULT_ROUTE } = require('../../routes')
class Notice extends Component {
constructor (props) {
super(props)
this.state = {
disclaimerDisabled: true,
}
}
componentWillMount () {
if (!this.props.notice) {
this.props.history.push(DEFAULT_ROUTE)
}
}
componentDidMount () {
// eslint-disable-next-line react/no-find-dom-node
var node = findDOMNode(this)
linker.setupListener(node)
if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) {
this.setState({ disclaimerDisabled: false })
}
}
componentWillReceiveProps (nextProps) {
if (!nextProps.notice) {
this.props.history.push(DEFAULT_ROUTE)
}
}
componentWillUnmount () {
// eslint-disable-next-line react/no-find-dom-node
var node = findDOMNode(this)
linker.teardownListener(node)
}
handleAccept () {
this.setState({ disclaimerDisabled: true })
this.props.onConfirm()
}
render () {
const { notice = {} } = this.props
const { title, date, body } = notice
const { disclaimerDisabled } = this.state
return (
h('.flex-column.flex-center.flex-grow', {
style: {
width: '100%',
},
}, [
h('h3.flex-center.text-transform-uppercase.terms-header', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
width: '100%',
fontSize: '20px',
textAlign: 'center',
padding: 6,
},
}, [
title,
]),
h('h5.flex-center.text-transform-uppercase.terms-header', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
marginBottom: 24,
width: '100%',
fontSize: '20px',
textAlign: 'center',
padding: 6,
},
}, [
date,
]),
h('style', `
.markdown {
overflow-x: hidden;
}
.markdown h1, .markdown h2, .markdown h3 {
margin: 10px 0;
font-weight: bold;
}
.markdown strong {
font-weight: bold;
}
.markdown em {
font-style: italic;
}
.markdown p {
margin: 10px 0;
}
.markdown a {
color: #df6b0e;
}
`),
h('div.markdown', {
onScroll: (e) => {
var object = e.currentTarget
if (object.offsetHeight + object.scrollTop + 100 >= object.scrollHeight) {
this.setState({ disclaimerDisabled: false })
}
},
style: {
background: 'rgb(235, 235, 235)',
height: '310px',
padding: '6px',
width: '90%',
overflowY: 'scroll',
scroll: 'auto',
},
}, [
h(ReactMarkdown, {
className: 'notice-box',
source: body,
skipHtml: true,
}),
]),
h('button.primary', {
disabled: disclaimerDisabled,
onClick: () => this.handleAccept(),
style: {
marginTop: '18px',
},
}, 'Accept'),
])
)
}
}
const mapStateToProps = state => {
const { metamask } = state
const { noActiveNotices, lastUnreadNotice, lostAccounts } = metamask
return {
noActiveNotices,
lastUnreadNotice,
lostAccounts,
}
}
Notice.propTypes = {
notice: PropTypes.object,
onConfirm: PropTypes.func,
history: PropTypes.object,
}
const mapDispatchToProps = dispatch => {
return {
markNoticeRead: lastUnreadNotice => dispatch(actions.markNoticeRead(lastUnreadNotice)),
markAccountsFound: () => dispatch(actions.markAccountsFound()),
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { noActiveNotices, lastUnreadNotice, lostAccounts } = stateProps
const { markNoticeRead, markAccountsFound } = dispatchProps
let notice
let onConfirm
if (!noActiveNotices) {
notice = lastUnreadNotice
onConfirm = () => markNoticeRead(lastUnreadNotice)
} else if (lostAccounts && lostAccounts.length > 0) {
notice = generateLostAccountsNotice(lostAccounts)
onConfirm = () => markAccountsFound()
}
return {
...stateProps,
...dispatchProps,
...ownProps,
notice,
onConfirm,
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps, mergeProps)(Notice)

View File

@ -0,0 +1,59 @@
const { Component } = require('react')
const { Switch, Route, matchPath } = require('react-router-dom')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const TabBar = require('../../tab-bar')
const Settings = require('./settings')
const Info = require('./info')
const { DEFAULT_ROUTE, SETTINGS_ROUTE, INFO_ROUTE } = require('../../../routes')
class Config extends Component {
renderTabs () {
const { history, location } = this.props
return h('div.settings__tabs', [
h(TabBar, {
tabs: [
{ content: 'Settings', key: SETTINGS_ROUTE },
{ content: 'Info', key: INFO_ROUTE },
],
isActive: key => matchPath(location.pathname, { path: key, exact: true }),
onSelect: key => history.push(key),
}),
])
}
render () {
const { history } = this.props
return (
h('.main-container.settings', {}, [
h('.settings__header', [
h('div.settings__close-button', {
onClick: () => history.push(DEFAULT_ROUTE),
}),
this.renderTabs(),
]),
h(Switch, [
h(Route, {
exact: true,
path: INFO_ROUTE,
component: Info,
}),
h(Route, {
exact: true,
path: SETTINGS_ROUTE,
component: Settings,
}),
]),
])
)
}
}
Config.propTypes = {
location: PropTypes.object,
history: PropTypes.object,
}
module.exports = Config

View File

@ -0,0 +1,112 @@
const { Component } = require('react')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
class Info extends Component {
renderLogo () {
return (
h('div.settings__info-logo-wrapper', [
h('img.settings__info-logo', { src: 'images/info-logo.png' }),
])
)
}
renderInfoLinks () {
return (
h('div.settings__content-item.settings__content-item--without-height', [
h('div.settings__info-link-header', this.context.t('links')),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/privacy.html',
target: '_blank',
}, [
h('span.settings__info-link', this.context.t('privacyMsg')),
]),
]),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/terms.html',
target: '_blank',
}, [
h('span.settings__info-link', this.context.t('terms')),
]),
]),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/attributions.html',
target: '_blank',
}, [
h('span.settings__info-link', this.context.t('attributions')),
]),
]),
h('hr.settings__info-separator'),
h('div.settings__info-link-item', [
h('a', {
href: 'https://support.metamask.io',
target: '_blank',
}, [
h('span.settings__info-link', this.context.t('supportCenter')),
]),
]),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/',
target: '_blank',
}, [
h('span.settings__info-link', this.context.t('visitWebSite')),
]),
]),
h('div.settings__info-link-item', [
h('a', {
target: '_blank',
href: 'mailto:help@metamask.io?subject=Feedback',
}, [
h('span.settings__info-link', this.context.t('emailUs')),
]),
]),
])
)
}
render () {
return (
h('div.settings__content', [
h('div.settings__content-row', [
h('div.settings__content-item.settings__content-item--without-height', [
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-item', [
h(
'div.settings__info-about',
this.context.t('builtInCalifornia')
),
]),
]),
this.renderInfoLinks(),
]),
])
)
}
}
Info.propTypes = {
tab: PropTypes.string,
metamask: PropTypes.object,
setCurrentCurrency: PropTypes.func,
setRpcTarget: PropTypes.func,
displayWarning: PropTypes.func,
revealSeedConfirmation: PropTypes.func,
warning: PropTypes.string,
location: PropTypes.object,
history: PropTypes.object,
t: PropTypes.func,
}
Info.contextTypes = {
t: PropTypes.func,
}
module.exports = Info

View File

@ -1,16 +1,18 @@
const { Component } = require('react')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('./actions')
const infuraCurrencies = require('./infura-conversion.json')
const actions = require('../../../actions')
const infuraCurrencies = require('../../../infura-conversion.json')
const validUrl = require('valid-url')
const { exportAsFile } = require('./util')
const TabBar = require('./components/tab-bar')
const SimpleDropdown = require('./components/dropdowns/simple-dropdown')
const { exportAsFile } = require('../../../util')
const SimpleDropdown = require('../../dropdowns/simple-dropdown')
const ToggleButton = require('react-toggle-button')
const locales = require('../../app/_locales/index.json')
const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums
const { REVEAL_SEED_ROUTE } = require('../../../routes')
const locales = require('../../../../../app/_locales/index.json')
const { OLD_UI_NETWORK_TYPE } = require('../../../../../app/scripts/config').enums
const getInfuraCurrencyOptions = () => {
const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {
@ -40,30 +42,11 @@ class Settings extends Component {
constructor (props) {
super(props)
const { tab } = props
const activeTab = tab === 'info' ? 'info' : 'settings'
this.state = {
activeTab,
newRpc: '',
}
}
renderTabs () {
const { activeTab } = this.state
return h('div.settings__tabs', [
h(TabBar, {
tabs: [
{ content: this.context.t('settings'), key: 'settings' },
{ content: this.context.t('info'), key: 'info' },
],
defaultTab: activeTab,
tabSelected: key => this.setState({ activeTab: key }),
}),
])
}
renderBlockieOptIn () {
const { metamask: { useBlockie }, setUseBlockie } = this.props
@ -253,7 +236,7 @@ class Settings extends Component {
}
renderSeedWords () {
const { revealSeedConfirmation } = this.props
const { history } = this.props
return (
h('div.settings__content-row', [
@ -261,9 +244,9 @@ class Settings extends Component {
h('div.settings__content-item', [
h('div.settings__content-item-col', [
h('button.btn-primary--lg.settings__button--red', {
onClick (event) {
onClick: event => {
event.preventDefault()
revealSeedConfirmation()
history.push(REVEAL_SEED_ROUTE)
},
}, this.context.t('revealSeedWords')),
]),
@ -310,7 +293,7 @@ class Settings extends Component {
])
}
renderSettingsContent () {
render () {
const { warning, isMascara } = this.props
return (
@ -328,120 +311,9 @@ class Settings extends Component {
])
)
}
renderLogo () {
return (
h('div.settings__info-logo-wrapper', [
h('img.settings__info-logo', { src: 'images/info-logo.png' }),
])
)
}
renderInfoLinks () {
return (
h('div.settings__content-item.settings__content-item--without-height', [
h('div.settings__info-link-header', this.context.t('links')),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/privacy.html',
target: '_blank',
}, [
h('span.settings__info-link', this.context.t('privacyMsg')),
]),
]),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/terms.html',
target: '_blank',
}, [
h('span.settings__info-link', this.context.t('terms')),
]),
]),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/attributions.html',
target: '_blank',
}, [
h('span.settings__info-link', this.context.t('attributions')),
]),
]),
h('hr.settings__info-separator'),
h('div.settings__info-link-item', [
h('a', {
href: 'https://support.metamask.io',
target: '_blank',
}, [
h('span.settings__info-link', this.context.t('supportCenter')),
]),
]),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/',
target: '_blank',
}, [
h('span.settings__info-link', this.context.t('visitWebSite')),
]),
]),
h('div.settings__info-link-item', [
h('a', {
target: '_blank',
href: 'mailto:help@metamask.io?subject=Feedback',
}, [
h('span.settings__info-link', this.context.t('emailUs')),
]),
]),
])
)
}
renderInfoContent () {
const version = global.platform.getVersion()
return (
h('div.settings__content', [
h('div.settings__content-row', [
h('div.settings__content-item.settings__content-item--without-height', [
this.renderLogo(),
h('div.settings__info-item', [
h('div.settings__info-version-header', 'MetaMask Version'),
h('div.settings__info-version-number', `${version}`),
]),
h('div.settings__info-item', [
h(
'div.settings__info-about',
this.context.t('builtInCalifornia')
),
]),
]),
this.renderInfoLinks(),
]),
])
)
}
render () {
const { goHome } = this.props
const { activeTab } = this.state
return (
h('.main-container.settings', {}, [
h('.settings__header', [
h('div.settings__close-button', {
onClick: goHome,
}),
this.renderTabs(),
]),
activeTab === 'settings'
? this.renderSettingsContent()
: this.renderInfoContent(),
])
)
}
}
Settings.propTypes = {
tab: PropTypes.string,
metamask: PropTypes.object,
setUseBlockie: PropTypes.func,
setCurrentCurrency: PropTypes.func,
@ -451,7 +323,7 @@ Settings.propTypes = {
setFeatureFlagToBeta: PropTypes.func,
showResetAccountConfirmationModal: PropTypes.func,
warning: PropTypes.string,
goHome: PropTypes.func,
history: PropTypes.object,
isMascara: PropTypes.bool,
updateCurrentLocale: PropTypes.func,
currentLocale: PropTypes.string,
@ -469,7 +341,6 @@ const mapStateToProps = state => {
const mapDispatchToProps = dispatch => {
return {
goHome: () => dispatch(actions.goHome()),
setCurrentCurrency: currency => dispatch(actions.setCurrentCurrency(currency)),
setRpcTarget: newRpc => dispatch(actions.setRpcTarget(newRpc)),
displayWarning: warning => dispatch(actions.displayWarning(warning)),
@ -490,5 +361,7 @@ Settings.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(Settings)
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(Settings)

View File

@ -0,0 +1,193 @@
const { Component } = require('react')
const PropTypes = require('prop-types')
const connect = require('../../metamask-connect')
const h = require('react-hyperscript')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const {
tryUnlockMetamask,
forgotPassword,
markPasswordForgotten,
setNetworkEndpoints,
setFeatureFlag,
} = require('../../actions')
const environmentType = require('../../../../app/scripts/lib/environment-type')
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 { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../routes')
class UnlockScreen extends Component {
constructor (props) {
super(props)
this.state = {
error: null,
}
this.animationEventEmitter = new EventEmitter()
}
componentWillMount () {
const { isUnlocked, history } = this.props
if (isUnlocked) {
history.push(DEFAULT_ROUTE)
}
}
componentDidMount () {
const passwordBox = document.getElementById('password-box')
if (passwordBox) {
passwordBox.focus()
}
}
tryUnlockMetamask (password) {
const { tryUnlockMetamask, history } = this.props
tryUnlockMetamask(password)
.then(() => history.push(DEFAULT_ROUTE))
.catch(({ message }) => this.setState({ error: message }))
}
onSubmit (event) {
const input = document.getElementById('password-box')
const password = input.value
this.tryUnlockMetamask(password)
}
onKeyPress (event) {
if (event.key === 'Enter') {
this.submitPassword(event)
}
}
submitPassword (event) {
var element = event.target
var password = element.value
// reset input
element.value = ''
this.tryUnlockMetamask(password)
}
inputChanged (event) {
// tell mascot to look at page action
var element = event.target
var boundingRect = element.getBoundingClientRect()
var coordinates = getCaretCoordinates(element, element.selectionEnd)
this.animationEventEmitter.emit('point', {
x: boundingRect.left + coordinates.left - element.scrollLeft,
y: boundingRect.top + coordinates.top - element.scrollTop,
})
}
render () {
const { error } = this.state
return (
h('.unlock-screen', [
h(Mascot, {
animationEventEmitter: this.animationEventEmitter,
}),
h('h1', {
style: {
fontSize: '1.4em',
textTransform: 'uppercase',
color: '#7F8082',
},
}, this.props.t('appName')),
h('input.large-input', {
type: 'password',
id: 'password-box',
placeholder: 'enter password',
style: {
background: 'white',
},
onKeyPress: this.onKeyPress.bind(this),
onInput: this.inputChanged.bind(this),
}),
h('.error', {
style: {
display: error ? 'block' : 'none',
padding: '0 20px',
textAlign: 'center',
},
}, error),
h('button.primary.cursor-pointer', {
onClick: this.onSubmit.bind(this),
style: {
margin: 10,
},
}, this.props.t('login')),
h('p.pointer', {
onClick: () => {
this.props.markPasswordForgotten()
this.props.history.push(RESTORE_VAULT_ROUTE)
if (environmentType() === 'popup') {
global.platform.openExtensionInBrowser()
}
},
style: {
fontSize: '0.8em',
color: 'rgb(247, 134, 28)',
textDecoration: 'underline',
},
}, this.props.t('restoreFromSeed')),
h('p.pointer', {
onClick: () => {
this.props.useOldInterface()
.then(() => this.props.setNetworkEndpoints(OLD_UI_NETWORK_TYPE))
},
style: {
fontSize: '0.8em',
color: '#aeaeae',
textDecoration: 'underline',
marginTop: '32px',
},
}, this.props.t('classicInterface')),
])
)
}
}
UnlockScreen.propTypes = {
forgotPassword: PropTypes.func,
tryUnlockMetamask: PropTypes.func,
markPasswordForgotten: PropTypes.func,
history: PropTypes.object,
isUnlocked: PropTypes.bool,
t: PropTypes.func,
useOldInterface: PropTypes.func,
setNetworkEndpoints: PropTypes.func,
}
const mapStateToProps = state => {
const { metamask: { isUnlocked } } = state
return {
isUnlocked,
}
}
const mapDispatchToProps = dispatch => {
return {
forgotPassword: () => dispatch(forgotPassword()),
tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)),
markPasswordForgotten: () => dispatch(markPasswordForgotten()),
useOldInterface: () => dispatch(setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL')),
setNetworkEndpoints: type => dispatch(setNetworkEndpoints(type)),
}
}
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(UnlockScreen)

View File

@ -1,4 +1,6 @@
const Component = require('react').Component
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const PropTypes = require('prop-types')
const connect = require('react-redux').connect
const h = require('react-hyperscript')
@ -23,12 +25,16 @@ const SenderToRecipient = require('../sender-to-recipient')
const NetworkDisplay = require('../network-display')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
ConfirmSendEther.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(ConfirmSendEther)
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(ConfirmSendEther)
function mapStateToProps (state) {
@ -72,7 +78,6 @@ function mapDispatchToProps (dispatch) {
errors: { to: null, amount: null },
editingTransactionId: id,
}))
dispatch(actions.showSendPage())
},
cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })),
showCustomizeGasModal: (txMeta, sendGasLimit, sendGasPrice, sendGasTotal) => {
@ -237,6 +242,7 @@ ConfirmSendEther.prototype.getData = function () {
const { identities } = this.props
const txMeta = this.gatherTxMeta()
const txParams = txMeta.txParams || {}
const account = identities ? identities[txParams.from] || {} : {}
const { FIAT: gasFeeInFIAT, ETH: gasFeeInETH, gasFeeInHex } = this.getGasFee()
const { FIAT: amountInFIAT, ETH: amountInETH } = this.getAmount()
@ -252,7 +258,7 @@ ConfirmSendEther.prototype.getData = function () {
return {
from: {
address: txParams.from,
name: identities[txParams.from].name,
name: account.name,
},
to: {
address: txParams.to,
@ -269,9 +275,14 @@ ConfirmSendEther.prototype.getData = function () {
}
}
ConfirmSendEther.prototype.editTransaction = function (txMeta) {
const { editTransaction, history } = this.props
editTransaction(txMeta)
history.push(SEND_ROUTE)
}
ConfirmSendEther.prototype.render = function () {
const {
editTransaction,
currentCurrency,
clearSend,
conversionRate,
@ -327,7 +338,7 @@ ConfirmSendEther.prototype.render = function () {
h('.page-container__header', [
h('.page-container__header-row', [
h('span.page-container__back-button', {
onClick: () => editTransaction(txMeta),
onClick: () => this.editTransaction(txMeta),
style: {
visibility: !txMeta.lastGasPrice ? 'initial' : 'hidden',
},
@ -505,7 +516,9 @@ ConfirmSendEther.prototype.render = function () {
}, this.context.t('cancel')),
// Accept Button
h('button.btn-confirm.page-container__footer-button.allcaps', [this.context.t('confirm')]),
h('button.btn-confirm.page-container__footer-button.allcaps', {
onClick: event => this.onSubmit(event),
}, this.context.t('confirm')),
]),
]),
])
@ -543,6 +556,7 @@ ConfirmSendEther.prototype.cancel = function (event, txMeta) {
const { cancelTransaction } = this.props
cancelTransaction(txMeta)
.then(() => this.props.history.push(DEFAULT_ROUTE))
}
ConfirmSendEther.prototype.isBalanceSufficient = function (txMeta) {
@ -630,4 +644,4 @@ ConfirmSendEther.prototype.bnMultiplyByFraction = function (targetBN, numerator,
const numBN = new BN(numerator)
const denomBN = new BN(denominator)
return targetBN.mul(numBN).div(denomBN)
}
}

View File

@ -1,4 +1,6 @@
const Component = require('react').Component
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const PropTypes = require('prop-types')
const connect = require('react-redux').connect
const h = require('react-hyperscript')
@ -33,12 +35,16 @@ const {
getSelectedAddress,
getSelectedTokenContract,
} = require('../../selectors')
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
ConfirmSendToken.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(ConfirmSendToken)
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(ConfirmSendToken)
function mapStateToProps (state, ownProps) {
@ -147,6 +153,12 @@ function ConfirmSendToken () {
this.onSubmit = this.onSubmit.bind(this)
}
ConfirmSendToken.prototype.editTransaction = function (txMeta) {
const { editTransaction, history } = this.props
editTransaction(txMeta)
history.push(SEND_ROUTE)
}
ConfirmSendToken.prototype.updateComponentSendErrors = function (prevProps) {
const {
balance: oldBalance,
@ -406,7 +418,6 @@ ConfirmSendToken.prototype.renderErrorMessage = function (message) {
}
ConfirmSendToken.prototype.render = function () {
const { editTransaction } = this.props
const txMeta = this.gatherTxMeta()
const {
from: {
@ -433,7 +444,7 @@ ConfirmSendToken.prototype.render = function () {
h('div.page-container', [
h('div.page-container__header', [
!txMeta.lastGasPrice && h('button.confirm-screen-back-button', {
onClick: () => editTransaction(txMeta),
onClick: () => this.editTransaction(txMeta),
}, this.context.t('edit')),
h('div.page-container__title', title),
h('div.page-container__subtitle', subtitle),
@ -513,7 +524,9 @@ ConfirmSendToken.prototype.render = function () {
}, this.context.t('cancel')),
// Accept Button
h('button.btn-confirm.page-container__footer-button.allcaps', [this.context.t('confirm')]),
h('button.btn-confirm.page-container__footer-button.allcaps', {
onClick: event => this.onSubmit(event),
}, [this.context.t('confirm')]),
]),
]),
]),
@ -566,6 +579,7 @@ ConfirmSendToken.prototype.cancel = function (event, txMeta) {
const { cancelTransaction } = this.props
cancelTransaction(txMeta)
.then(() => this.props.history.push(DEFAULT_ROUTE))
}
ConfirmSendToken.prototype.checkValidity = function () {

View File

@ -2,6 +2,8 @@ const connect = require('react-redux').connect
const actions = require('../../actions')
const abi = require('ethereumjs-abi')
const SendEther = require('../../send-v2')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const {
accountsWithSendEtherInfoSelector,
@ -16,7 +18,10 @@ const {
getSelectedTokenContract,
} = require('../../selectors')
module.exports = connect(mapStateToProps, mapDispatchToProps)(SendEther)
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(SendEther)
function mapStateToProps (state) {
const fromAccounts = accountsWithSendEtherInfoSelector(state)
@ -79,7 +84,6 @@ function mapDispatchToProps (dispatch) {
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
updateSendMemo: newMemo => dispatch(actions.updateSendMemo(newMemo)),
updateSendErrors: newError => dispatch(actions.updateSendErrors(newError)),
goHome: () => dispatch(actions.goHome()),
clearSend: () => dispatch(actions.clearSend()),
setMaxModeTo: bool => dispatch(actions.setMaxModeTo(bool)),
}

View File

@ -6,6 +6,8 @@ const Identicon = require('./identicon')
const connect = require('react-redux').connect
const ethUtil = require('ethereumjs-util')
const classnames = require('classnames')
const { compose } = require('recompose')
const { withRouter } = require('react-router-dom')
const AccountDropdownMini = require('./dropdowns/account-dropdown-mini')
@ -20,6 +22,8 @@ const {
conversionRateSelector,
} = require('../selectors.js')
const { DEFAULT_ROUTE } = require('../routes')
function mapStateToProps (state) {
return {
balance: getSelectedAccount(state).balance,
@ -42,7 +46,10 @@ SignatureRequest.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(SignatureRequest)
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(SignatureRequest)
inherits(SignatureRequest, Component)
@ -229,10 +236,14 @@ SignatureRequest.prototype.renderFooter = function () {
return h('div.request-signature__footer', [
h('button.btn-secondary--lg.request-signature__footer__cancel-button', {
onClick: cancel,
onClick: event => {
cancel(event).then(() => this.props.history.push(DEFAULT_ROUTE))
},
}, this.context.t('cancel')),
h('button.btn-primary--lg', {
onClick: sign,
onClick: event => {
sign(event).then(() => this.props.history.push(DEFAULT_ROUTE))
},
}, this.context.t('sign')),
])
}

View File

@ -4,31 +4,17 @@ const PropTypes = require('prop-types')
const classnames = require('classnames')
class TabBar extends Component {
constructor (props) {
super(props)
const { defaultTab, tabs } = props
this.state = {
subview: defaultTab || tabs[0].key,
}
}
render () {
const { tabs = [], tabSelected } = this.props
const { subview } = this.state
const { tabs = [], onSelect, isActive } = this.props
return (
h('.tab-bar', {}, [
tabs.map((tab) => {
const { key, content } = tab
tabs.map(({ key, content }) => {
return h('div', {
className: classnames('tab-bar__tab pointer', {
'tab-bar__tab--active': subview === key,
'tab-bar__tab--active': isActive(key, content),
}),
onClick: () => {
this.setState({ subview: key })
tabSelected(key)
},
onClick: () => onSelect(key),
key,
}, content)
}),
@ -39,9 +25,9 @@ class TabBar extends Component {
}
TabBar.propTypes = {
defaultTab: PropTypes.string,
isActive: PropTypes.func.isRequired,
tabs: PropTypes.array,
tabSelected: PropTypes.func,
onSelect: PropTypes.func,
}
module.exports = TabBar

View File

@ -11,14 +11,19 @@ const { formatDate } = require('../util')
const { showConfTxPage } = require('../actions')
const classnames = require('classnames')
const { tokenInfoGetter } = require('../token-util')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const { CONFIRM_TRANSACTION_ROUTE } = require('../routes')
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(TxList)
TxList.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(TxList)
function mapStateToProps (state) {
return {
txsToRender: selectors.transactionsSelector(state),
@ -96,7 +101,7 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa
transactionNetworkId,
transactionSubmittedTime,
} = props
const { showConfTxPage } = this.props
const { history } = this.props
const opts = {
key: transactionId || transactionHash,
@ -116,7 +121,10 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa
const isUnapproved = transactionStatus === 'unapproved'
if (isUnapproved) {
opts.onClick = () => showConfTxPage({ id: transactionId })
opts.onClick = () => {
this.props.showConfTxPage({ id: transactionId })
history.push(CONFIRM_TRANSACTION_ROUTE)
}
opts.transactionStatus = this.context.t('notStarted')
} else if (transactionHash) {
opts.onClick = () => this.view(transactionHash, transactionNetworkId)

View File

@ -4,20 +4,25 @@ const connect = require('react-redux').connect
const h = require('react-hyperscript')
const ethUtil = require('ethereumjs-util')
const inherits = require('util').inherits
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const actions = require('../actions')
const selectors = require('../selectors')
const { SEND_ROUTE } = require('../routes')
const BalanceComponent = require('./balance-component')
const TxList = require('./tx-list')
const Identicon = require('./identicon')
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(TxView)
TxView.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(TxView)
function mapStateToProps (state) {
const sidebarOpen = state.appState.sidebarOpen
const isMascara = state.appState.isMascara
@ -69,7 +74,7 @@ TxView.prototype.renderHeroBalance = function () {
}
TxView.prototype.renderButtons = function () {
const {selectedToken, showModal, showSendPage, showSendTokenPage } = this.props
const {selectedToken, showModal, history } = this.props
return !selectedToken
? (
@ -84,14 +89,14 @@ TxView.prototype.renderButtons = function () {
style: {
marginLeft: '0.8em',
},
onClick: showSendPage,
onClick: () => history.push(SEND_ROUTE),
}, this.context.t('send')),
])
)
: (
h('div.flex-row.flex-center.hero-balance-buttons', [
h('button.btn-primary.hero-balance-button', {
onClick: showSendTokenPage,
onClick: () => history.push(SEND_ROUTE),
}, this.context.t('send')),
])
)

View File

@ -2,6 +2,8 @@ const Component = require('react').Component
const PropTypes = require('prop-types')
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const inherits = require('util').inherits
const classnames = require('classnames')
const Identicon = require('./identicon')
@ -12,14 +14,17 @@ const actions = require('../actions')
const BalanceComponent = require('./balance-component')
const TokenList = require('./token-list')
const selectors = require('../selectors')
const { ADD_TOKEN_ROUTE } = require('../routes')
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(WalletView)
WalletView.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(WalletView)
function mapStateToProps (state) {
return {
@ -97,7 +102,7 @@ WalletView.prototype.render = function () {
keyrings,
showAccountDetailModal,
hideSidebar,
showAddTokenPage,
history,
} = this.props
// temporary logs + fake extra wallets
// console.log('walletview, selectedAccount:', selectedAccount)
@ -174,10 +179,7 @@ WalletView.prototype.render = function () {
h(TokenList),
h('button.btn-primary.wallet-view__add-token-button', {
onClick: () => {
showAddTokenPage()
hideSidebar()
},
onClick: () => history.push(ADD_TOKEN_ROUTE),
}, this.context.t('addToken')),
])
}

View File

@ -2,6 +2,8 @@ const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const actions = require('./actions')
const txHelper = require('../lib/tx-helper')
@ -11,19 +13,21 @@ const SignatureRequest = require('./components/signature-request')
// const PendingPersonalMsg = require('./components/pending-personal-msg')
// const PendingTypedMsg = require('./components/pending-typed-msg')
const Loading = require('./components/loading')
const { DEFAULT_ROUTE } = require('./routes')
// const contentDivider = h('div', {
// style: {
// marginLeft: '16px',
// marginRight: '16px',
// height:'1px',
// background:'#E7E7E7',
// },
// })
module.exports = connect(mapStateToProps)(ConfirmTxScreen)
module.exports = compose(
withRouter,
connect(mapStateToProps)
)(ConfirmTxScreen)
function mapStateToProps (state) {
const { metamask } = state
const {
unapprovedMsgCount,
unapprovedPersonalMsgCount,
unapprovedTypedMessagesCount,
} = metamask
return {
identities: state.metamask.identities,
accounts: state.metamask.accounts,
@ -40,6 +44,10 @@ function mapStateToProps (state) {
currentCurrency: state.metamask.currentCurrency,
blockGasLimit: state.metamask.currentBlockGasLimit,
computedBalances: state.metamask.computedBalances,
unapprovedMsgCount,
unapprovedPersonalMsgCount,
unapprovedTypedMessagesCount,
send: state.metamask.send,
selectedAddressTxList: state.metamask.selectedAddressTxList,
}
}
@ -49,11 +57,35 @@ function ConfirmTxScreen () {
Component.call(this)
}
ConfirmTxScreen.prototype.getUnapprovedMessagesTotal = function () {
const {
unapprovedMsgCount = 0,
unapprovedPersonalMsgCount = 0,
unapprovedTypedMessagesCount = 0,
} = this.props
return unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount
}
ConfirmTxScreen.prototype.componentDidMount = function () {
const {
unapprovedTxs = {},
network,
send,
} = this.props
const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network)
if (unconfTxList.length === 0 && !send.to && this.getUnapprovedMessagesTotal() === 0) {
this.props.history.push(DEFAULT_ROUTE)
}
}
ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) {
const {
unapprovedTxs,
unapprovedTxs = {},
network,
selectedAddressTxList,
send,
} = this.props
const { index: prevIndex, unapprovedTxs: prevUnapprovedTxs } = prevProps
const prevUnconfTxList = txHelper(prevUnapprovedTxs, {}, {}, {}, network)
@ -61,8 +93,9 @@ ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) {
const prevTx = selectedAddressTxList.find(({ id }) => id === prevTxData.id) || {}
const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network)
if (prevTx.status === 'dropped' && unconfTxList.length === 0) {
this.goHome({})
if (unconfTxList.length === 0 &&
(prevTx.status === 'dropped' || !send.to && this.getUnapprovedMessagesTotal() === 0)) {
this.props.history.push(DEFAULT_ROUTE)
}
}
@ -103,7 +136,6 @@ ConfirmTxScreen.prototype.render = function () {
*/
log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`)
if (unconfTxList.length === 0) return h(Loading)
return currentTxView({
// Properties
@ -152,6 +184,7 @@ function currentTxView (opts) {
// return h(PendingTypedMsg, opts)
// }
}
return h(Loading)
}
@ -163,6 +196,7 @@ ConfirmTxScreen.prototype.buyEth = function (address, event) {
ConfirmTxScreen.prototype.sendTransaction = function (txData, event) {
this.stopPropagation(event)
this.props.dispatch(actions.updateAndApproveTx(txData))
.then(() => this.props.history.push(DEFAULT_ROUTE))
}
ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) {
@ -182,7 +216,7 @@ ConfirmTxScreen.prototype.signMessage = function (msgData, event) {
var params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
this.props.dispatch(actions.signMsg(params))
return this.props.dispatch(actions.signMsg(params))
}
ConfirmTxScreen.prototype.stopPropagation = function (event) {
@ -196,7 +230,7 @@ ConfirmTxScreen.prototype.signPersonalMessage = function (msgData, event) {
var params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
this.props.dispatch(actions.signPersonalMsg(params))
return this.props.dispatch(actions.signPersonalMsg(params))
}
ConfirmTxScreen.prototype.signTypedMessage = function (msgData, event) {
@ -204,25 +238,25 @@ ConfirmTxScreen.prototype.signTypedMessage = function (msgData, event) {
var params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
this.props.dispatch(actions.signTypedMsg(params))
return this.props.dispatch(actions.signTypedMsg(params))
}
ConfirmTxScreen.prototype.cancelMessage = function (msgData, event) {
log.info('canceling message')
this.stopPropagation(event)
this.props.dispatch(actions.cancelMsg(msgData))
return this.props.dispatch(actions.cancelMsg(msgData))
}
ConfirmTxScreen.prototype.cancelPersonalMessage = function (msgData, event) {
log.info('canceling personal message')
this.stopPropagation(event)
this.props.dispatch(actions.cancelPersonalMsg(msgData))
return this.props.dispatch(actions.cancelPersonalMsg(msgData))
}
ConfirmTxScreen.prototype.cancelTypedMessage = function (msgData, event) {
log.info('canceling typed message')
this.stopPropagation(event)
this.props.dispatch(actions.cancelTypedMsg(msgData))
return this.props.dispatch(actions.cancelTypedMsg(msgData))
}
ConfirmTxScreen.prototype.goHome = function (event) {

View File

@ -6,9 +6,15 @@
*/
@import './itcss/settings/index.scss';
@import './itcss/tools/index.scss';
@import './itcss/generic/index.scss';
@import './itcss/base/index.scss';
@import './itcss/objects/index.scss';
@import './itcss/components/index.scss';
@import './itcss/trumps/index.scss';

View File

@ -8,6 +8,7 @@
font-family: 'Roboto';
background: white;
border-radius: 8px;
box-shadow: 0 0 7px 0 rgba(0, 0, 0, 0.08);
&__wrapper {
background-color: $white;
@ -20,7 +21,7 @@
&__header {
display: flex;
flex-flow: column nowrap;
padding: 16px 16px 0px;
padding: 20px 20px 0px;
border-bottom: 1px solid $geyser;
flex: 0 0 auto;
@ -31,7 +32,8 @@
span {
font-family: Roboto;
font-size: 16px;
font-size: 16px;
font-weight: 400;
line-height: 21px;
margin-left: 8px;
}
@ -44,8 +46,13 @@
margin-top: 4px;
}
&__subtitle {
font-weight: 400;
margin-top: 15px;
margin-bottom: 21px;
}
&__tabs {
margin-left: 22px;
display: flex;
&__tab {
@ -54,6 +61,7 @@
color: $dusty-gray;
font-family: Roboto;
font-size: 18px;
font-weight: 400;
line-height: 24px;
text-align: center;
}
@ -65,6 +73,7 @@
&__unselected:hover {
color: $black;
border-bottom: none;
cursor: pointer;
}
&__selected {
@ -76,7 +85,7 @@
&__info-box {
height: 96px;
margin: 20px 24px 0px;
margin: 20px 20px 0px;
border-radius: 4px;
background-color: $alabaster;
position: relative;
@ -98,6 +107,7 @@
color: $mid-gray;
font-family: Roboto;
font-size: 14px;
font-weight: 400;
margin-top: 15px;
margin-bottom: 9px;
}
@ -107,6 +117,7 @@
color: $mid-gray;
font-family: Roboto;
font-size: 12px;
font-weight: 400;
line-height: 18px;
}
@ -124,7 +135,8 @@
}
&__confirmation-description {
margin: 12px 0;
font-weight: 400;
margin: 20px 0 40px 0;
}
&__content-container {
@ -151,7 +163,7 @@
&__input,
&__add-custom-input {
height: 54px;
padding: 21px 6px;
padding: 0px 20px;
border: 1px solid $geyser;
border-radius: 4px;
margin: 22px 24px;
@ -232,6 +244,7 @@
&__add-custom-label {
font-size: 16px;
font-weight: 400;
line-height: 21px;
margin-left: 22px;
color: $scorpion;
@ -274,9 +287,11 @@
color: #5B5D67;
font-family: Roboto;
font-size: 18px;
font-weight: 400;
line-height: 24px;
margin-left: 24px;
margin-top: 8px;
margin-bottom: 20px;
}
&__token-icons-container {
@ -317,6 +332,7 @@
}
&__token-name {
font-weight: 400;
font-size: 14px;
line-height: 19px;
}
@ -368,6 +384,7 @@
&__symbol {
color: $scorpion;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}

View File

@ -52,6 +52,8 @@
@import './editable-label.scss';
@import './pages/index.scss';
@import './new-account.scss';
@import './tooltip.scss';

View File

@ -35,13 +35,14 @@
font-size: 18px;
line-height: 24px;
text-align: center;
cursor: pointer;
}
&__tab:first-of-type {
margin-right: 20px;
}
&__unselected:hover {
&__tab:hover {
color: $black;
border-bottom: none;
}
@ -49,9 +50,9 @@
&__selected {
color: $curious-blue;
border-bottom: 3px solid $curious-blue;
cursor: initial;
}
}
}
.new-account-import-disclaimer {

View File

@ -0,0 +1 @@
@import './unlock.scss';

View File

@ -0,0 +1,9 @@
.unlock-page {
box-shadow: none;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgb(247, 247, 247);
width: 100%;
}

View File

@ -17,6 +17,12 @@ textarea.twelve-word-phrase {
resize: none;
}
.initialize-screen {
width: 100%;
z-index: $main-container-z-index;
background: #f7f7f7;
}
.initialize-screen hr {
width: 60px;
margin: 12px;

View File

@ -1,6 +1,5 @@
const inherits = require('util').inherits
const EventEmitter = require('events').EventEmitter
const Component = require('react').Component
const { EventEmitter } = require('events')
const { Component } = require('react')
const PropTypes = require('prop-types')
const connect = require('react-redux').connect
const h = require('react-hyperscript')
@ -8,205 +7,219 @@ const Mascot = require('../components/mascot')
const actions = require('../actions')
const Tooltip = require('../components/tooltip')
const getCaretCoordinates = require('textarea-caret')
const { RESTORE_VAULT_ROUTE, DEFAULT_ROUTE } = require('../routes')
const environmentType = require('../../../app/scripts/lib/environment-type')
const { OLD_UI_NETWORK_TYPE } = require('../../../app/scripts/config').enums
let isSubmitting = false
class InitializeMenuScreen extends Component {
constructor (props) {
super(props)
this.animationEventEmitter = new EventEmitter()
this.state = {
warning: null,
}
}
componentWillMount () {
const { isInitialized, isUnlocked, history } = this.props
if (isInitialized || isUnlocked) {
history.push(DEFAULT_ROUTE)
}
}
componentDidMount () {
document.getElementById('password-box').focus()
}
render () {
const { warning } = this.state
return (
h('.initialize-screen.flex-column.flex-center', [
h(Mascot, {
animationEventEmitter: this.animationEventEmitter,
}),
h('h1', {
style: {
fontSize: '1.3em',
textTransform: 'uppercase',
color: '#7F8082',
marginBottom: 10,
},
}, this.context.t('appName')),
h('div', [
h('h3', {
style: {
fontSize: '0.8em',
color: '#7F8082',
display: 'inline',
},
}, this.context.t('encryptNewDen')),
h(Tooltip, {
title: this.context.t('denExplainer'),
}, [
h('i.fa.fa-question-circle.pointer', {
style: {
fontSize: '18px',
position: 'relative',
color: 'rgb(247, 134, 28)',
top: '2px',
marginLeft: '4px',
},
}),
]),
]),
h('span.error.in-progress-notification', warning),
// password
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box',
placeholder: this.context.t('newPassword'),
onInput: this.inputChanged.bind(this),
style: {
width: 260,
marginTop: 12,
},
}),
// confirm password
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box-confirm',
placeholder: this.context.t('confirmPassword'),
onKeyPress: this.createVaultOnEnter.bind(this),
onInput: this.inputChanged.bind(this),
style: {
width: 260,
marginTop: 16,
},
}),
h('button.primary', {
onClick: this.createNewVaultAndKeychain.bind(this),
style: {
margin: 12,
},
}, this.context.t('createDen')),
h('.flex-row.flex-center.flex-grow', [
h('p.pointer', {
onClick: () => this.showRestoreVault(),
style: {
fontSize: '0.8em',
color: 'rgb(247, 134, 28)',
textDecoration: 'underline',
},
}, this.context.t('importDen')),
]),
h('.flex-row.flex-center.flex-grow', [
h('p.pointer', {
onClick: this.showOldUI.bind(this),
style: {
fontSize: '0.8em',
color: '#aeaeae',
textDecoration: 'underline',
marginTop: '32px',
},
}, 'Use classic interface'),
]),
])
)
}
createVaultOnEnter (event) {
if (event.key === 'Enter') {
event.preventDefault()
this.createNewVaultAndKeychain()
}
}
createNewVaultAndKeychain () {
const { history } = this.props
var passwordBox = document.getElementById('password-box')
var password = passwordBox.value
var passwordConfirmBox = document.getElementById('password-box-confirm')
var passwordConfirm = passwordConfirmBox.value
this.setState({ warning: null })
if (password.length < 8) {
this.setState({ warning: this.context.t('passwordShort') })
return
}
if (password !== passwordConfirm) {
this.setState({ warning: this.context.t('passwordMismatch') })
return
}
this.props.createNewVaultAndKeychain(password)
.then(() => history.push(DEFAULT_ROUTE))
}
inputChanged (event) {
// tell mascot to look at page action
var element = event.target
var boundingRect = element.getBoundingClientRect()
var coordinates = getCaretCoordinates(element, element.selectionEnd)
this.animationEventEmitter.emit('point', {
x: boundingRect.left + coordinates.left - element.scrollLeft,
y: boundingRect.top + coordinates.top - element.scrollTop,
})
}
showRestoreVault () {
this.props.markPasswordForgotten()
if (environmentType() === 'popup') {
global.platform.openExtensionInBrowser()
}
this.props.history.push(RESTORE_VAULT_ROUTE)
}
showOldUI () {
this.props.dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
.then(() => this.props.dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)))
}
}
InitializeMenuScreen.propTypes = {
history: PropTypes.object,
isInitialized: PropTypes.bool,
isUnlocked: PropTypes.bool,
createNewVaultAndKeychain: PropTypes.func,
markPasswordForgotten: PropTypes.func,
dispatch: PropTypes.func,
}
InitializeMenuScreen.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps)(InitializeMenuScreen)
const mapStateToProps = state => {
const { metamask: { isInitialized, isUnlocked } } = state
inherits(InitializeMenuScreen, Component)
function InitializeMenuScreen () {
Component.call(this)
this.animationEventEmitter = new EventEmitter()
}
function mapStateToProps (state) {
return {
// state from plugin
currentView: state.appState.currentView,
warning: state.appState.warning,
isInitialized,
isUnlocked,
}
}
InitializeMenuScreen.prototype.render = function () {
var state = this.props
switch (state.currentView.name) {
default:
return this.renderMenu(state)
const mapDispatchToProps = dispatch => {
return {
createNewVaultAndKeychain: password => dispatch(actions.createNewVaultAndKeychain(password)),
markPasswordForgotten: () => dispatch(actions.markPasswordForgotten()),
}
}
// InitializeMenuScreen.prototype.componentDidMount = function(){
// document.getElementById('password-box').focus()
// }
InitializeMenuScreen.prototype.renderMenu = function (state) {
return (
h('.initialize-screen.flex-column.flex-center.flex-grow', [
h(Mascot, {
animationEventEmitter: this.animationEventEmitter,
}),
h('h1', {
style: {
fontSize: '1.3em',
textTransform: 'uppercase',
color: '#7F8082',
marginBottom: 10,
},
}, this.context.t('appName')),
h('div', [
h('h3', {
style: {
fontSize: '0.8em',
color: '#7F8082',
display: 'inline',
},
}, this.context.t('encryptNewDen')),
h(Tooltip, {
title: this.context.t('denExplainer'),
}, [
h('i.fa.fa-question-circle.pointer', {
style: {
fontSize: '18px',
position: 'relative',
color: 'rgb(247, 134, 28)',
top: '2px',
marginLeft: '4px',
},
}),
]),
]),
h('span.in-progress-notification', state.warning),
// password
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box',
placeholder: this.context.t('newPassword'),
onInput: this.inputChanged.bind(this),
style: {
width: 260,
marginTop: 12,
},
}),
// confirm password
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box-confirm',
placeholder: this.context.t('confirmPassword'),
onKeyPress: this.createVaultOnEnter.bind(this),
onInput: this.inputChanged.bind(this),
style: {
width: 260,
marginTop: 16,
},
}),
h('button.primary', {
onClick: this.createNewVaultAndKeychain.bind(this),
style: {
margin: 12,
},
}, this.context.t('createDen')),
h('.flex-row.flex-center.flex-grow', [
h('p.pointer', {
onClick: this.showRestoreVault.bind(this),
style: {
fontSize: '0.8em',
color: 'rgb(247, 134, 28)',
textDecoration: 'underline',
},
}, this.context.t('importDen')),
]),
h('.flex-row.flex-center.flex-grow', [
h('p.pointer', {
onClick: this.showOldUI.bind(this),
style: {
fontSize: '0.8em',
color: '#aeaeae',
textDecoration: 'underline',
marginTop: '32px',
},
}, 'Use classic interface'),
]),
])
)
}
InitializeMenuScreen.prototype.createVaultOnEnter = function (event) {
if (event.key === 'Enter') {
event.preventDefault()
this.createNewVaultAndKeychain()
}
}
InitializeMenuScreen.prototype.componentDidMount = function () {
document.getElementById('password-box').focus()
}
InitializeMenuScreen.prototype.showRestoreVault = function () {
this.props.dispatch(actions.markPasswordForgotten())
if (environmentType() === 'popup') {
global.platform.openExtensionInBrowser()
}
}
InitializeMenuScreen.prototype.showOldUI = function () {
this.props.dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
.then(() => this.props.dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)))
}
InitializeMenuScreen.prototype.createNewVaultAndKeychain = function () {
var passwordBox = document.getElementById('password-box')
var password = passwordBox.value
var passwordConfirmBox = document.getElementById('password-box-confirm')
var passwordConfirm = passwordConfirmBox.value
if (password.length < 8) {
this.warning = this.context.t('passwordShort')
this.props.dispatch(actions.displayWarning(this.warning))
return
}
if (password !== passwordConfirm) {
this.warning = this.context.t('passwordMismatch')
this.props.dispatch(actions.displayWarning(this.warning))
return
}
if (!isSubmitting) {
isSubmitting = true
this.props.dispatch(actions.createNewVaultAndKeychain(password))
}
}
InitializeMenuScreen.prototype.inputChanged = function (event) {
// tell mascot to look at page action
var element = event.target
var boundingRect = element.getBoundingClientRect()
var coordinates = getCaretCoordinates(element, element.selectionEnd)
this.animationEventEmitter.emit('point', {
x: boundingRect.left + coordinates.left - element.scrollLeft,
y: boundingRect.top + coordinates.top - element.scrollTop,
})
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(InitializeMenuScreen)

View File

@ -1,6 +1,8 @@
const { Component } = require('react')
const connect = require('react-redux').connect
const PropTypes = require('prop-types')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const t = require('../i18n-helper').getMessage
class I18nProvider extends Component {
@ -32,5 +34,8 @@ const mapStateToProps = state => {
}
}
module.exports = connect(mapStateToProps)(I18nProvider)
module.exports = compose(
withRouter,
connect(mapStateToProps)
)(I18nProvider)

View File

@ -4,12 +4,21 @@ const PropTypes = require('prop-types')
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('../../../actions')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const {
DEFAULT_ROUTE,
INITIALIZE_BACKUP_PHRASE_ROUTE,
} = require('../../../routes')
RevealSeedConfirmation.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps)(RevealSeedConfirmation)
module.exports = compose(
withRouter,
connect(mapStateToProps)
)(RevealSeedConfirmation)
inherits(RevealSeedConfirmation, Component)
@ -109,6 +118,8 @@ RevealSeedConfirmation.prototype.componentDidMount = function () {
RevealSeedConfirmation.prototype.goHome = function () {
this.props.dispatch(actions.showConfigPage(false))
this.props.dispatch(actions.confirmSeedWords())
.then(() => this.props.history.push(DEFAULT_ROUTE))
}
// create vault
@ -123,4 +134,5 @@ RevealSeedConfirmation.prototype.checkConfirmation = function (event) {
RevealSeedConfirmation.prototype.revealSeedWords = function () {
var password = document.getElementById('password-box').value
this.props.dispatch(actions.requestRevealSeed(password))
.then(() => this.props.history.push(INITIALIZE_BACKUP_PHRASE_ROUTE))
}

View File

@ -2,8 +2,8 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const AccountAndTransactionDetails = require('./account-and-transaction-details')
const Settings = require('./settings')
const UnlockScreen = require('./unlock')
const Settings = require('./components/pages/settings')
const UnlockScreen = require('./components/pages/unlock')
module.exports = MainContainer

View File

@ -1,22 +1,23 @@
const inherits = require('util').inherits
const Component = require('react').Component
const Provider = require('react-redux').Provider
const { Component } = require('react')
const PropTypes = require('prop-types')
const { Provider } = require('react-redux')
const h = require('react-hyperscript')
const SelectedApp = require('./select-app')
module.exports = Root
class Root extends Component {
render () {
const { store } = this.props
inherits(Root, Component)
function Root () { Component.call(this) }
Root.prototype.render = function () {
return (
h(Provider, {
store: this.props.store,
}, [
h(SelectedApp),
])
)
return (
h(Provider, { store }, [
h(SelectedApp),
])
)
}
}
Root.propTypes = {
store: PropTypes.object,
}
module.exports = Root

49
ui/app/routes.js Normal file
View File

@ -0,0 +1,49 @@
const DEFAULT_ROUTE = '/'
const UNLOCK_ROUTE = '/unlock'
const SETTINGS_ROUTE = '/settings'
const INFO_ROUTE = '/settings/info'
const REVEAL_SEED_ROUTE = '/seed'
const CONFIRM_SEED_ROUTE = '/confirm-seed'
const RESTORE_VAULT_ROUTE = '/restore-vault'
const ADD_TOKEN_ROUTE = '/add-token'
const NEW_ACCOUNT_ROUTE = '/new-account'
const IMPORT_ACCOUNT_ROUTE = '/new-account/import'
const SEND_ROUTE = '/send'
const CONFIRM_TRANSACTION_ROUTE = '/confirm-transaction'
const SIGNATURE_REQUEST_ROUTE = '/confirm-transaction/signature-request'
const NOTICE_ROUTE = '/notice'
const WELCOME_ROUTE = '/welcome'
const INITIALIZE_ROUTE = '/initialize'
const INITIALIZE_CREATE_PASSWORD_ROUTE = '/initialize/create-password'
const INITIALIZE_IMPORT_ACCOUNT_ROUTE = '/initialize/import-account'
const INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE = '/initialize/import-with-seed-phrase'
const INITIALIZE_UNIQUE_IMAGE_ROUTE = '/initialize/unique-image'
const INITIALIZE_NOTICE_ROUTE = '/initialize/notice'
const INITIALIZE_BACKUP_PHRASE_ROUTE = '/initialize/backup-phrase'
const INITIALIZE_CONFIRM_SEED_ROUTE = '/initialize/confirm-phrase'
module.exports = {
DEFAULT_ROUTE,
UNLOCK_ROUTE,
SETTINGS_ROUTE,
INFO_ROUTE,
REVEAL_SEED_ROUTE,
CONFIRM_SEED_ROUTE,
RESTORE_VAULT_ROUTE,
ADD_TOKEN_ROUTE,
NEW_ACCOUNT_ROUTE,
IMPORT_ACCOUNT_ROUTE,
SEND_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
NOTICE_ROUTE,
SIGNATURE_REQUEST_ROUTE,
WELCOME_ROUTE,
INITIALIZE_ROUTE,
INITIALIZE_CREATE_PASSWORD_ROUTE,
INITIALIZE_IMPORT_ACCOUNT_ROUTE,
INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE,
INITIALIZE_UNIQUE_IMAGE_ROUTE,
INITIALIZE_NOTICE_ROUTE,
INITIALIZE_BACKUP_PHRASE_ROUTE,
INITIALIZE_CONFIRM_SEED_ROUTE,
}

View File

@ -2,6 +2,7 @@ const inherits = require('util').inherits
const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const { HashRouter } = require('react-router-dom')
const App = require('./app')
const OldApp = require('../../old-ui/app/app')
const { autoAddToBetaUI } = require('./selectors')
@ -63,7 +64,12 @@ SelectedApp.prototype.render = function () {
// const Selected = betaUI || isMascara || firstTime ? App : OldApp
const { betaUI, isMascara } = this.props
const Selected = betaUI || isMascara ? h(I18nProvider, [ h(App) ]) : h(OldApp)
return Selected
return betaUI || isMascara
? h(HashRouter, {
hashType: 'noslash',
}, [
h(I18nProvider, [ h(App) ]),
])
: h(OldApp)
}

View File

@ -30,6 +30,7 @@ const {
getGasTotal,
} = require('./components/send/send-utils')
const { isValidAddress } = require('./util')
const { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } = require('./routes')
SendTransactionScreen.contextTypes = {
t: PropTypes.func,
@ -182,7 +183,7 @@ SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) {
}
SendTransactionScreen.prototype.renderHeader = function () {
const { selectedToken, clearSend, goHome } = this.props
const { selectedToken, clearSend, history } = this.props
return h('div.page-container__header', [
@ -193,7 +194,7 @@ SendTransactionScreen.prototype.renderHeader = function () {
h('div.page-container__header-close', {
onClick: () => {
clearSend()
goHome()
history.push(DEFAULT_ROUTE)
},
}),
@ -254,7 +255,6 @@ SendTransactionScreen.prototype.handleToChange = function (to, nickname = '') {
const {
updateSendTo,
updateSendErrors,
from: {address: from},
} = this.props
let toError = null
@ -262,8 +262,6 @@ SendTransactionScreen.prototype.handleToChange = function (to, nickname = '') {
toError = this.context.t('required')
} else if (!isValidAddress(to)) {
toError = this.context.t('invalidAddressRecipient')
} else if (to === from) {
toError = this.context.t('fromToSame')
}
updateSendTo(to, nickname)
@ -498,12 +496,12 @@ SendTransactionScreen.prototype.renderForm = function () {
SendTransactionScreen.prototype.renderFooter = function () {
const {
goHome,
clearSend,
gasTotal,
tokenBalance,
selectedToken,
errors: { amount: amountError, to: toError },
history,
} = this.props
const missingTokenBalance = selectedToken && !tokenBalance
@ -513,7 +511,7 @@ SendTransactionScreen.prototype.renderFooter = function () {
h('button.btn-secondary--lg.page-container__footer-button', {
onClick: () => {
clearSend()
goHome()
history.push(DEFAULT_ROUTE)
},
}, this.context.t('cancel')),
h('button.btn-primary--lg.page-container__footer-button', {
@ -579,12 +577,17 @@ SendTransactionScreen.prototype.getEditedTx = function () {
data,
})
} else {
const data = unapprovedTxs[editingTransactionId].txParams.data
const { data } = unapprovedTxs[editingTransactionId].txParams
Object.assign(editingTx.txParams, {
value: ethUtil.addHexPrefix(amount),
to: ethUtil.addHexPrefix(to),
data,
})
if (typeof editingTx.txParams.data === 'undefined') {
delete editingTx.txParams.data
}
}
return editingTx
@ -619,7 +622,6 @@ SendTransactionScreen.prototype.onSubmit = function (event) {
if (editingTransactionId) {
const editedTx = this.getEditedTx()
updateTx(editedTx)
} else {
@ -643,4 +645,6 @@ SendTransactionScreen.prototype.onSubmit = function (event) {
? signTokenTx(selectedToken.address, to, amount, txParams)
: signTx(txParams)
}
this.props.history.push(CONFIRM_TRANSACTION_ROUTE)
}

View File

@ -271,6 +271,7 @@ function exportAsFile (filename, data) {
window.navigator.msSaveBlob(blob, filename)
} else {
const elem = window.document.createElement('a')
elem.target = '_blank'
elem.href = window.URL.createObjectURL(blob)
elem.download = filename
document.body.appendChild(elem)

View File

@ -3,21 +3,35 @@ import h from 'react-hyperscript'
import { Component } from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
import {closeWelcomeScreen} from './actions'
import Mascot from './components/mascot'
import { INITIALIZE_CREATE_PASSWORD_ROUTE } from './routes'
class WelcomeScreen extends Component {
static propTypes = {
closeWelcomeScreen: PropTypes.func.isRequired,
welcomeScreenSeen: PropTypes.bool,
history: PropTypes.object,
}
constructor(props) {
constructor (props) {
super(props)
this.animationEventEmitter = new EventEmitter()
}
componentWillMount () {
const { history, welcomeScreenSeen } = this.props
if (welcomeScreenSeen) {
history.push(INITIALIZE_CREATE_PASSWORD_ROUTE)
}
}
initiateAccountCreation = () => {
this.props.closeWelcomeScreen()
this.props.history.push(INITIALIZE_CREATE_PASSWORD_ROUTE)
}
render () {
@ -48,9 +62,18 @@ class WelcomeScreen extends Component {
}
}
export default connect(
null,
dispatch => ({
closeWelcomeScreen: () => dispatch(closeWelcomeScreen()),
})
const mapStateToProps = ({ metamask: { welcomeScreenSeen } }) => {
return {
welcomeScreenSeen,
}
}
export default compose(
withRouter,
connect(
mapStateToProps,
dispatch => ({
closeWelcomeScreen: () => dispatch(closeWelcomeScreen()),
})
)
)(WelcomeScreen)

1898
yarn.lock

File diff suppressed because it is too large Load Diff