diff options
Diffstat (limited to 'app')
25 files changed, 1356 insertions, 143 deletions
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 34575b4dd..b372326ee 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -56,7 +56,7 @@ "message": "Balance:" }, "balances": { - "message": "Your balances" + "message": "Token balance(s)" }, "balanceIsInsufficientGas": { "message": "Insufficient balance for current gas total" diff --git a/app/_locales/index.json b/app/_locales/index.json index c085deb72..727504bda 100644 --- a/app/_locales/index.json +++ b/app/_locales/index.json @@ -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" } diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json new file mode 100644 index 000000000..c2c09bc91 --- /dev/null +++ b/app/_locales/tr/messages.json @@ -0,0 +1,912 @@ +{ + "accept": { + "message": "Kabul et" + }, + "account": { + "message": "Hesap" + }, + "accountDetails": { + "message": "Hesap Detayları" + }, + "accountName": { + "message": "Hesap İsmi" + }, + "address": { + "message": "Adres" + }, + "addCustomToken": { + "message": "Özel jeton ekle" + }, + "addToken": { + "message": "Jeton ekle" + }, + "addTokens": { + "message": "Jetonlar ekle" + }, + "amount": { + "message": "Tutar" + }, + "amountPlusGas": { + "message": "Tutar + Gas" + }, + "appDescription": { + "message": "Ethereum Tarayıcı Uzantısı", + "description": "Uygulama açıklaması" + }, + "appName": { + "message": "MetaMask", + "description": "Uygulama ismi" + }, + "approved": { + "message": "Onaylandı" + }, + "attemptingConnect": { + "message": "Blockchain'e bağlanmayı deniyor" + }, + "attributions": { + "message": "Atıflar" + }, + "available": { + "message": "Müsait" + }, + "back": { + "message": "Geri" + }, + "balance": { + "message": "Bakiye:" + }, + "balances": { + "message": "Jeton bakiyesi" + }, + "balanceIsInsufficientGas": { + "message": "Toplam gas için yetersiz bakiye" + }, + "beta": { + "message": "BETA" + }, + "betweenMinAndMax": { + "message": "$1'e eşit veya daha büyük olmalı ve $2'den küçük veya eşit olmalı", + "description": "Onaltılık sayının ondalık sayı olarak girişi için yardımcı" + }, + "blockiesIdenticon": { + "message": "Blockies Identicon kullan" + }, + "borrowDharma": { + "message": "Dharma (Beta) ile ödünç al" + }, + "builtInCalifornia": { + "message": "MetaMask California'da tasarlandı ve yaratıldı" + }, + "buy": { + "message": "Satın al" + }, + "buyCoinbase": { + "message": "Coinbase'de satın al" + }, + "buyCoinbaseExplainer": { + "message": "Coinbase bitcoin, ethereum, and litecoin alıp satmanın dünyadaki en popüler yolu" + }, + "ok": { + "message": "Tamam" + }, + "cancel": { + "message": "Vazgeç" + }, + "classicInterface": { + "message": "Klasik arayüzü kullan" + }, + "clickCopy": { + "message": "Kopyalamak için tıkla" + }, + "confirm": { + "message": "Onayla" + }, + "confirmed": { + "message": "Onaylandı" + }, + "confirmContract": { + "message": "Sözleşmeyi onayla" + }, + "confirmPassword": { + "message": "Şifreyi onayla" + }, + "confirmTransaction": { + "message": "İşlemi onayla" + }, + "continue": { + "message": "Devam et" + }, + "continueToCoinbase": { + "message": "Coinbase'e devam et" + }, + "contractDeployment": { + "message": "Sözleşme kurulumu" + }, + "conversionProgress": { + "message": "Çevirim devam ediyor" + }, + "copiedButton": { + "message": "Kopyalandı" + }, + "copiedClipboard": { + "message": "Panoya kopyalandı" + }, + "copiedExclamation": { + "message": "Kopyalandı!" + }, + "copiedSafe": { + "message": "Güvenli bir yere kopyaladım" + }, + "copy": { + "message": "Kopyala" + }, + "copyToClipboard": { + "message": "Panoya kopyala" + }, + "copyButton": { + "message": " Kopyala " + }, + "copyPrivateKey": { + "message": "Bu sizin özel anahtarınız (kopyalamak için tıklayın)" + }, + "create": { + "message": "Yarat" + }, + "createAccount": { + "message": "Hesap Oluştur" + }, + "createDen": { + "message": "Yarat" + }, + "crypto": { + "message": "Kripto", + "description": "Kambiyo tipi (kripto para)" + }, + "currentConversion": { + "message": "Geçerli çevirme" + }, + "currentNetwork": { + "message": "Geçerli Ağ" + }, + "customGas": { + "message": "Gas'i özelleştir" + }, + "customToken": { + "message": "Özel Jeton" + }, + "customize": { + "message": "Özelleştir" + }, + "customRPC": { + "message": "Özel RPC" + }, + "decimalsMustZerotoTen": { + "message": "Ondalıklar en azından 0 olmalı ve 36'dan büyük olmamalı." + }, + "decimal": { + "message": "Ondalık hassasiyeti" + }, + "defaultNetwork": { + "message": "Ether işlemleri için varsayılan ağ Main Net." + }, + "denExplainer": { + "message": "DEN'iniz MetaMask içersinde parola-şifrelenmiş deponuzdur." + }, + "deposit": { + "message": "Yatır" + }, + "depositBTC": { + "message": "BTC'inizi aşağıdaki adrese yatırın:" + }, + "depositCoin": { + "message": "$1'nızı aşağıdaki adrese yatırın", + "description": "Kullanıcıya hangi jetonu seçtiyse onu yatırmasını shapeshift ile söyler." + }, + "depositEth": { + "message": "Eth yatır" + }, + "depositEther": { + "message": "Ether yatır" + }, + "depositFiat": { + "message": "Para yatır" + }, + "depositFromAccount": { + "message": "Başka bir hesaptan yatır" + }, + "depositShapeShift": { + "message": "ShapeShift ile yatır" + }, + "depositShapeShiftExplainer": { + "message": "Eğer başka kripto paralara sahipseniz, MetaMask cüzdanınıza direk olarak Ether yatırabilirsiniz. Hesaba gerek yoktur." + }, + "details": { + "message": "Ayrıntılar" + }, + "directDeposit": { + "message": "Direk Yatırma" + }, + "directDepositEther": { + "message": "Direk Ether Yatırma" + }, + "directDepositEtherExplainer": { + "message": "Eğer çoktan Etheriniz varsa, yeni hesabınıza Ether aktarmanın en kolay yolu direk yatırmadır." + }, + "done": { + "message": "Bitti" + }, + "downloadStateLogs": { + "message": "Durum kayıtlarını indir" + }, + "dropped": { + "message": "Bırakıldı" + }, + "edit": { + "message": "Düzenle" + }, + "editAccountName": { + "message": "Hesap ismini düzenle" + }, + "emailUs": { + "message": "Bize e-posta atın!" + }, + "encryptNewDen": { + "message": "Yeni DEN'inizi şifreleyin" + }, + "enterPassword": { + "message": "Parolanızı girin" + }, + "enterPasswordConfirm": { + "message": "Onaylamak için parolanızı girin" + }, + "passwordNotLongEnough": { + "message": "Parola yeterince uzun değil" + }, + "passwordsDontMatch": { + "message": "Parolalar eşleşmiyor" + }, + "etherscanView": { + "message": "Hesabı Etherscan üzerinde izle" + }, + "exchangeRate": { + "message": "Döviz kuru" + }, + "exportPrivateKey": { + "message": "Özel anahtarı ver" + }, + "exportPrivateKeyWarning": { + "message": "Özel anahtarınızı vermek sizin sorumluluğunuzdadır." + }, + "failed": { + "message": "Başarısız oldu" + }, + "fiat": { + "message": "Para", + "description": "Döviz türü" + }, + "fileImportFail": { + "message": "Dosya alma çalışmıyor mu? Buraya tıklayın!", + "description": "Kullanıcıların hesaplarını JSON dosyasından almalarına yardım eder" + }, + "followTwitter": { + "message": "Bizi twitter'da takip edin" + }, + "from": { + "message": "Kimden" + }, + "fromToSame": { + "message": "Kimden ve kime adresi aynı olamaz" + }, + "fromShapeShift": { + "message": "ShapeShift'den" + }, + "gas": { + "message": "Gas", + "description": "Gas maliyetinin kısa indikatörü" + }, + "gasFee": { + "message": "Gas Ücreti" + }, + "gasLimit": { + "message": "Gas Limiti" + }, + "gasLimitCalculation": { + "message": "Önerilen gas limitini ağ başarı oranını baz alarak hesaplıyoruz." + }, + "gasLimitRequired": { + "message": "Gas limiti gereklidir" + }, + "gasLimitTooLow": { + "message": "Gas limiti en az 21000 olmalıdır" + }, + "generatingSeed": { + "message": "Kaynak Oluşturuyor..." + }, + "gasPrice": { + "message": "Gas Fiyatı (GWEI)" + }, + "gasPriceCalculation": { + "message": "Önerilen gas fiyatını ağ başarı oranını baz alarak hesaplıyoruz." + }, + "gasPriceRequired": { + "message": "Gas Fiyatı Gereklidir" + }, + "getEther": { + "message": "Ether Al" + }, + "getEtherFromFaucet": { + "message": "Musluktan $1 karşılığı Ether alın", + "description": "Ether musluğunun ağ ismini gösterir" + }, + "greaterThanMin": { + "message": "must be greater than or equal to $1.", + "description": "helper for inputting hex as decimal input" + }, + "here": { + "message": "burada", + "description": "daha fazla bilgi için -buraya tıklayın- (troubleTokenBalances ile gidiyor)" + }, + "hereList": { + "message": "İşte bir liste!!!!" + }, + "hide": { + "message": "Gizle" + }, + "hideToken": { + "message": "Jetonu gizle" + }, + "hideTokenPrompt": { + "message": "Jetonu gizle?" + }, + "howToDeposit": { + "message": "Ether'i nasıl yatırmak istersiniz?" + }, + "holdEther": { + "message": "Ether ve jeton tutmanızı sağlar ve merkezi olmayan uygulamalar ve sizin aranızda köprü vazifesi görür." + }, + "import": { + "message": "Al", + "description": "Seçilen dosyadan hesap alma düğmesi. " + }, + "importAccount": { + "message": "Hesap Al" + }, + "importAccountMsg": { + "message":" Alınan hesaplar orjinal kaynakifadenizle yarattığınız MetaMask hesabınızla ilişkilendirilmez. Alınan hesaplar ile ilgili daha fazla bilgi edinin " + }, + "importAnAccount": { + "message": "Hesap al" + }, + "importDen": { + "message": "Varolan DEN al" + }, + "imported": { + "message": "Alındı", + "description": "Hesabın keyringe başarı ile alındığını gösteren durum" + }, + "infoHelp": { + "message": "Bilgi ve yardım" + }, + "insufficientFunds": { + "message": "Yetersiz kaynak." + }, + "insufficientTokens": { + "message": "Yetersiz Jeton." + }, + "invalidAddress": { + "message": "Geçersiz adres" + }, + "invalidAddressRecipient": { + "message": "Alıcı adresi geçersiz" + }, + "invalidGasParams": { + "message": "Geçersiz gas parametreleri" + }, + "invalidInput": { + "message": "Geçersiz giriş." + }, + "invalidRequest": { + "message": "Geçersiz istek" + }, + "invalidRPC": { + "message": "Geçersiz RPC URI" + }, + "jsonFail": { + "message": "Birşeyler yanlış gitti. JSON dosyanızın düzgün derlendiğinden emin olun." + }, + "jsonFile": { + "message": "JSON Dosyası", + "description": "Hesap alımı için düzenle" + }, + "keepTrackTokens": { + "message": "MetaMask hesabınızla satın aldığınız jetonların kaydını tutun." + }, + "kovan": { + "message": "Kovan Test Ağı" + }, + "knowledgeDataBase": { + "message": "Bilgi veritabanımızı ziyaret edin" + }, + "max": { + "message": "Maksimum" + }, + "learnMore": { + "message": "Daha fazla bilgi." + }, + "lessThanMax": { + "message": "$1'den az veya eşit olmalıdır.", + "description": "Onaltılık sayıyı ondalık olarak girmek için yardımcı" + }, + "likeToAddTokens": { + "message": "Bu jetonlara adres eklemek ister misiniz?" + }, + "links": { + "message": "Bağlantılar" + }, + "limit": { + "message": "Limit" + }, + "loading": { + "message": "Yükleniyor..." + }, + "loadingTokens": { + "message": "Jetonlar yükleniyor..." + }, + "localhost": { + "message": "Localhost 8545" + }, + "login": { + "message": "Giriş yap" + }, + "logout": { + "message": "Çıkış" + }, + "loose": { + "message": "Gevşek" + }, + "loweCaseWords": { + "message": "kaynak kelimeleri sadece küçük harflerden oluşabilir." + }, + "mainnet": { + "message": "Main Ethereum Ağı" + }, + "message": { + "message": "Mesaj" + }, + "metamaskDescription": { + "message": "MetaMask Ethereum için güvenli bir kimlik kasasıdır." + }, + "min": { + "message": "Minimum" + }, + "myAccounts": { + "message": "Hesaplarım" + }, + "mustSelectOne": { + "message": "En az bir jeton seçilmeli" + }, + "needEtherInWallet": { + "message": "MetaMask kullanarak merkezi olamayan uygulamalarla etkileşmek için cüzdanınızda Ether bulunmalıdır." + }, + "needImportFile": { + "message": "Almak için bir dosya seçmelisiniz.", + "description": "Kullanıcı bir hesap alır ve devam etmek içinbir dosya seçmelidir." + }, + "needImportPassword": { + "message": "Seçilen dosya için bir parola girmelisiniz.", + "description": "Hesap almak için parola ve dosya gerekiyor." + }, + "negativeETH": { + "message": "Negatif ETH miktarları gönderilemez." + }, + "networks": { + "message": "Ağlar" + }, + "newAccount": { + "message": "Yeni Hesap" + }, + "newAccountNumberName": { + "message": "Hesap $1", + "description": "Hesap yaratma ekranındaki bir sonraki hesabın varsayılan ismi" + }, + "newContract": { + "message": "Yeni Sözleşme" + }, + "newPassword": { + "message": "Yeni Parola (min 8 karakter)" + }, + "newRecipient": { + "message": "Yeni alıcı" + }, + "newRPC": { + "message": "Yeni RPC URL" + }, + "next": { + "message": "Sonraki" + }, + "noAddressForName": { + "message": "Bu isim için bir adres tanımlanmamış." + }, + "noDeposits": { + "message": "Yatırma alınmadı" + }, + "noTransactionHistory": { + "message": "İşlem geçmişi yok." + }, + "noTransactions": { + "message": "İşlem yok" + }, + "notStarted": { + "message": "Başlamadı" + }, + "oldUI": { + "message": "Eski UI" + }, + "oldUIMessage": { + "message": "Eski UI'a döndünüz. Yeni UI'a üst sağ sekme menüsündeki seçenek ile dönebilirsiniz." + }, + "or": { + "message": "veya", + "description": "Yeni bir hesap yaratmak veya almak arasındaki seçim" + }, + "passwordCorrect": { + "message": "Lütfen parolanın doğru olduğuna emin olun." + }, + "passwordMismatch": { + "message": "parolalar eşleşmiyor", + "description": "parola yaratma işleminde, iki yeni parola alanı eşleşmiyor." + }, + "passwordShort": { + "message": "parola yeterince uzun değil", + "description": "parola yaratma işleminde, parola güvenli olacak kadar uzun değil." + }, + "pastePrivateKey": { + "message": "Özel anahtar dizinizi buraya yapıştırın:", + "description": "Özel anahtardan hesap almak için" + }, + "pasteSeed": { + "message": "Kaynak ifadenizi buraya yapıştırın!" + }, + "personalAddressDetected": { + "message": "Kişisel adres tespit edilidi. Jeton sözleşme adresini girin." + }, + "pleaseReviewTransaction": { + "message": "Lütfen işleminizi gözden geçirin." + }, + "popularTokens": { + "message": "Popüler Jetonlar" + }, + "privacyMsg": { + "message": "Gizlilik Şartları" + }, + "privateKey": { + "message": "Özel Anahtar", + "description": "Hesap alırken bu tip bir dosya seçin" + }, + "privateKeyWarning": { + "message": "Uyarı: Bu anahtarı kimse ile paylaşmayın. Özel anahtarlarınıza sahip herkes hesaplarınızıdaki tüm varlığınızı çalabilir." + }, + "privateNetwork": { + "message": "Özel Ağ" + }, + "qrCode": { + "message": "QR Kodunu göster" + }, + "readdToken": { + "message": "Gelecekte Bu jetonu hesap seçenekleri menüsünde “Jeton ekle”'ye giderek geri ekleyebilirsiniz." + }, + "readMore": { + "message": "Burada daha fazla okuyun." + }, + "readMore2": { + "message": "Daha fazla okuyun." + }, + "receive": { + "message": "Al" + }, + "recipientAddress": { + "message": "Alıcı adresi" + }, + "refundAddress": { + "message": "İade adresiniz" + }, + "rejected": { + "message": "Rededildi" + }, + "resetAccount": { + "message": "Hesabı sıfıla" + }, + "restoreFromSeed": { + "message": "Kaynak ifadeden geri getir. Restore from seed phrase" + }, + "restoreVault": { + "message": "Kasayı geri getir" + }, + "required": { + "message": "Gerekli" + }, + "retryWithMoreGas": { + "message": "Daha yüksek bir gas fiyatı ile tekrar dene" + }, + "walletSeed": { + "message": "Cüzdan Kaynağı" + }, + "revealSeedWords": { + "message": "Kaynak kelimelerini göster" + }, + "revealSeedWordsWarning": { + "message": "Açık bir yerde kaynak kelimeliriniz geri getirmeyin! Bu kelimeler tüm hesaplarınızı çalmak için kullanılabilir." + }, + "revert": { + "message": "Geri döndür" + }, + "rinkeby": { + "message": "Rinkeby Test Ağı" + }, + "ropsten": { + "message": "Ropsten Test Ağı" + }, + "currentRpc": { + "message": "Geçerli RPC" + }, + "connectingToMainnet": { + "message": "Main Ethereum Ağına bağlanıyor" + }, + "connectingToRopsten": { + "message": "Ropsten Test Ağına bağlanıyor" + }, + "connectingToKovan": { + "message": "Kovan Test Ağına bağlanıyor" + }, + "connectingToRinkeby": { + "message": "Rinkeby Test Ağına bağlanıyor" + }, + "connectingToUnknown": { + "message": "Bilinmeyen Ağa bağlanıyor" + }, + "sampleAccountName": { + "message": "E.g. Yeni hesabım", + "description": "Kullanıcının hesabına okunabilir isim ekleme konseptini anlamasına yardımcı olmak." + }, + "save": { + "message": "Kaydet" + }, + "reprice_title": { + "message": "İşlemi Yeniden Fiyatlandır" + }, + "reprice_subtitle": { + "message": "İşlemizi hızlandırmayı denemek için gas fiyatınızı yükseltin." + }, + "saveAsFile": { + "message": "Dosya olarak kaydet", + "description": "Hesap verme işlemi" + }, + "saveSeedAsFile": { + "message": "Kaynak Kelimelerini Dosya olarak Kaydet" + }, + "search": { + "message": "Ara" + }, + "secretPhrase": { + "message": "Kasanızı geri getirmek için gizli 12 kelimelik ifadenizi giriniz." + }, + "newPassword8Chars": { + "message": "Yeni Parola (minumum 8 karakter)" + }, + "seedPhraseReq": { + "message": "Kaynak ifadeleri 12 kelimedir." + }, + "select": { + "message": "Seç" + }, + "selectCurrency": { + "message": "Döviz Seç" + }, + "selectService": { + "message": "Servis Seç" + }, + "selectType": { + "message": "Tip Seç" + }, + "send": { + "message": "Gönder" + }, + "sendETH": { + "message": "ETH Gönder" + }, + "sendTokens": { + "message": "Jeton Gönder" + }, + "onlySendToEtherAddress": { + "message": "Ethereum adresine sadece ETH gönder." + }, + "searchTokens": { + "message": "Jeton ara" + }, + "sendTokensAnywhere": { + "message": "Ethereum hesabı olan birine Jeton gönder" + }, + "settings": { + "message": "Ayarlar" + }, + "info": { + "message": "Bilgi" + }, + "shapeshiftBuy": { + "message": "Shapeshift ile satın al" + }, + "showPrivateKeys": { + "message": "Özel anahtarları göster" + }, + "showQRCode": { + "message": "QR Kodu göster" + }, + "sign": { + "message": "İmza" + }, + "signed": { + "message": "İmzalandı" + }, + "signMessage": { + "message": "Mesajı İmzala" + }, + "signNotice": { + "message": "Bu mesajı imzalamanın tehlikeli \nyan etkileri olabilir. Tamamen güvendiğiniz sitelerden \ngelen mesajları hesabınızla imzalayınız.\n Bu tehlikeli metod gelecek versiyonlarda çıkarılacaktır. " + }, + "sigRequest": { + "message": "İmza isteği" + }, + "sigRequested": { + "message": "İmza isteniyor" + }, + "spaceBetween": { + "message": "Kelimeler arası sadece bir boşluk olabilir." + }, + "status": { + "message": "Durum" + }, + "stateLogs": { + "message": "Durum Kayıtları" + }, + "stateLogsDescription": { + "message": "Durum kayıtları açık hesap adresinizi ve gönderilen işlemleri içerir." + }, + "stateLogError": { + "message": "Durum kayıtlarını alma hatası" + }, + "submit": { + "message": "Gönder" + }, + "submitted": { + "message": "Gönderildi" + }, + "supportCenter": { + "message": "Destek merkezimizi ziyaret edin" + }, + "symbolBetweenZeroTen": { + "message": "Sembol 0 ve 10 karakter aralığında olmalıdır." + }, + "takesTooLong": { + "message": "Çok mu uzun sürüyor?" + }, + "terms": { + "message": "Kullanım şartları" + }, + "testFaucet": { + "message": "Test Musluğu" + }, + "to": { + "message": "Kime: " + }, + "toETHviaShapeShift": { + "message": "ShapeShift üstünden $1'dan ETH'e", + "description": "system will fill in deposit type in start of message" + }, + "tokenAddress": { + "message": "Jeton Adresi" + }, + "tokenAlreadyAdded": { + "message": "Jeton çoktan eklenmiş." + }, + "tokenBalance": { + "message": "Jeton bakiyeniz:" + }, + "tokenSelection": { + "message": "Jeton arayın veya popüler jeton listemizden seçin." + }, + "tokenSymbol": { + "message": "Jeton Sembolü" + }, + "tokenWarning1": { + "message": "MetaMask hesabınızla aldığınız jetonların kaydını tutun. Başka bir hesapla jetonlar satın aldıysanız, o jetonlar burada gözükmeyecektir." + }, + "total": { + "message": "Toplam" + }, + "transactions": { + "message": "işlemler" + }, + "transactionError": { + "message": "İşlem Hatası. Sözleşme kodundan kural dışı durum fırlatıldı." + }, + "transactionMemo": { + "message": "İşlem notu (opsiyonel)" + }, + "transactionNumber": { + "message": "İşlem numarası" + }, + "transfers": { + "message": "Transferler" + }, + "troubleTokenBalances": { + "message": "Jeton bakiyelerinizi yüklerken sorun yaşadık. Buradan izleyebilirsiniz ", + "description": "Jeton bakiyelerini görmek için bir link (burası) ile takip ediliyor" + }, + "twelveWords": { + "message": "MetaMask hesaplarınızı geri getirmenin tek yolu bu 12 kelimedir.\nBu kelimeleri güvenli ve gizli bir yerde saklayın." + }, + "typePassword": { + "message": "Parolanızı girin" + }, + "uiWelcome": { + "message": "Yeni UI (Beta)'ya hoşgeldiniz" + }, + "uiWelcomeMessage": { + "message": "Şu anda yeni Metamask UI kullanmaktasınız. Gözatın, jeton gönderme gibi yeni özellikleri deneyin ve herhangi bir sorunlar karşılaşırsanız bize haber verin" + }, + "unapproved": { + "message": "Onaylanmadı" + }, + "unavailable": { + "message": "Mevcut değil" + }, + "unknown": { + "message": "Bilinmeyen" + }, + "unknownNetwork": { + "message": "Bilinmeyen özel ağ" + }, + "unknownNetworkId": { + "message": "Bilinmeyen ağ IDsi" + }, + "uriErrorMsg": { + "message": "URIler için HTTP/HTTPS öneki gerekmektedir." + }, + "usaOnly": { + "message": "Sadece ABD", + "description": "Bu dövizi sadece ABD ikamet edenler kullanabilir" + }, + "usedByClients": { + "message": "Farklı istemciler tarafından kullanılmakta" + }, + "useOldUI": { + "message": "Eski UI kullan" + }, + "validFileImport": { + "message": "Almak için geçerli bir dosya seçmelisiniz" + }, + "vaultCreated": { + "message": "Kasa Yaratıldı" + }, + "viewAccount": { + "message": "Hesabı İncele" + }, + "visitWebSite": { + "message": "Web sitemizi ziyaret edin" + }, + "warning": { + "message": "Uyarı" + }, + "welcomeBeta": { + "message": "MetaMask Beta'ya Hoşgeldiniz" + }, + "whatsThis": { + "message": "Bu nedir?" + }, + "yourSigRequested": { + "message": "İmzanız isteniyor" + }, + "youSign": { + "message": "İmzalıyorsunuz" + } +}
\ No newline at end of file diff --git a/app/manifest.json b/app/manifest.json index 73496adfa..dc46f1ca4 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -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__", diff --git a/app/scripts/background.js b/app/scripts/background.js index 3ad0a7863..5878cd2e8 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -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) => { diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 2098fae27..fe1766273 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -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++) { diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 31e53554d..336b0d8f7 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -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 diff --git a/app/scripts/lib/get-first-preferred-lang-code.js b/app/scripts/lib/get-first-preferred-lang-code.js index 28612f763..e3635434e 100644 --- a/app/scripts/lib/get-first-preferred-lang-code.js +++ b/app/scripts/lib/get-first-preferred-lang-code.js @@ -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' } diff --git a/app/scripts/lib/getObjStructure.js b/app/scripts/lib/getObjStructure.js new file mode 100644 index 000000000..3db389507 --- /dev/null +++ b/app/scripts/lib/getObjStructure.js @@ -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 +} diff --git a/app/scripts/lib/is-popup-or-notification.js b/app/scripts/lib/is-popup-or-notification.js index e2999411f..ad3e825c0 100644 --- a/app/scripts/lib/is-popup-or-notification.js +++ b/app/scripts/lib/is-popup-or-notification.js @@ -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' diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index 4fd2cae92..85c2717ea 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -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 diff --git a/app/scripts/lib/tx-gas-utils.js b/app/scripts/lib/tx-gas-utils.js index 829b4c421..c579e462a 100644 --- a/app/scripts/lib/tx-gas-utils.js +++ b/app/scripts/lib/tx-gas-utils.js @@ -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 - } }
\ No newline at end of file diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js index 23c915a61..c6d10ee62 100644 --- a/app/scripts/lib/tx-state-manager.js +++ b/app/scripts/lib/tx-state-manager.js @@ -92,8 +92,10 @@ module.exports = class TransactionStateManager extends EventEmitter { // or rejected tx's. // not tx's that are pending or unapproved if (txCount > txHistoryLimit - 1) { - const index = transactions.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected') - transactions.splice(index, 1) + let index = transactions.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected') + if (index !== -1) { + transactions.splice(index, 1) + } } transactions.push(txMeta) this._saveTxList(transactions) @@ -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 + } }) } diff --git a/app/scripts/migrations/013.js b/app/scripts/migrations/013.js index 8f11e510e..15a9b28d4 100644 --- a/app/scripts/migrations/013.js +++ b/app/scripts/migrations/013.js @@ -27,8 +27,11 @@ module.exports = { function transformState (state) { const newState = state - if (newState.config.provider.type === 'testnet') { - newState.config.provider.type = 'ropsten' + const { config } = newState + if ( config && config.provider ) { + if (config.provider.type === 'testnet') { + newState.config.provider.type = 'ropsten' + } } return newState } diff --git a/app/scripts/migrations/015.js b/app/scripts/migrations/015.js index 4b839580b..5e2f9e63b 100644 --- a/app/scripts/migrations/015.js +++ b/app/scripts/migrations/015.js @@ -28,11 +28,14 @@ module.exports = { function transformState (state) { const newState = state - const transactions = newState.TransactionController.transactions - newState.TransactionController.transactions = transactions.map((txMeta) => { - if (!txMeta.err) return txMeta - else if (txMeta.err.message === 'Gave up submitting tx.') txMeta.status = 'failed' - return txMeta - }) + const { TransactionController } = newState + if (TransactionController && TransactionController.transactions) { + const transactions = TransactionController.transactions + newState.TransactionController.transactions = transactions.map((txMeta) => { + if (!txMeta.err) return txMeta + else if (txMeta.err.message === 'Gave up submitting tx.') txMeta.status = 'failed' + return txMeta + }) + } return newState } diff --git a/app/scripts/migrations/016.js b/app/scripts/migrations/016.js index 4fc534f1c..048c7a40e 100644 --- a/app/scripts/migrations/016.js +++ b/app/scripts/migrations/016.js @@ -28,14 +28,18 @@ module.exports = { function transformState (state) { const newState = state - const transactions = newState.TransactionController.transactions - newState.TransactionController.transactions = transactions.map((txMeta) => { - if (!txMeta.err) return txMeta - if (txMeta.err === 'transaction with the same hash was already imported.') { - txMeta.status = 'submitted' - delete txMeta.err - } - return txMeta - }) + const { TransactionController } = newState + if (TransactionController && TransactionController.transactions) { + const transactions = newState.TransactionController.transactions + + newState.TransactionController.transactions = transactions.map((txMeta) => { + if (!txMeta.err) return txMeta + if (txMeta.err === 'transaction with the same hash was already imported.') { + txMeta.status = 'submitted' + delete txMeta.err + } + return txMeta + }) + } return newState } diff --git a/app/scripts/migrations/017.js b/app/scripts/migrations/017.js index 24959cd3a..5f6d906d6 100644 --- a/app/scripts/migrations/017.js +++ b/app/scripts/migrations/017.js @@ -27,14 +27,17 @@ module.exports = { function transformState (state) { const newState = state - const transactions = newState.TransactionController.transactions - newState.TransactionController.transactions = transactions.map((txMeta) => { - if (!txMeta.status === 'failed') return txMeta - if (txMeta.retryCount > 0 && txMeta.retryCount < 2) { - txMeta.status = 'submitted' - delete txMeta.err - } - return txMeta - }) + const { TransactionController } = newState + if (TransactionController && TransactionController.transactions) { + const transactions = newState.TransactionController.transactions + newState.TransactionController.transactions = transactions.map((txMeta) => { + if (!txMeta.status === 'failed') return txMeta + if (txMeta.retryCount > 0 && txMeta.retryCount < 2) { + txMeta.status = 'submitted' + delete txMeta.err + } + return txMeta + }) + } return newState } diff --git a/app/scripts/migrations/018.js b/app/scripts/migrations/018.js index d27fe3f46..bea1fe3da 100644 --- a/app/scripts/migrations/018.js +++ b/app/scripts/migrations/018.js @@ -29,24 +29,27 @@ module.exports = { function transformState (state) { const newState = state - const transactions = newState.TransactionController.transactions - newState.TransactionController.transactions = transactions.map((txMeta) => { - // no history: initialize - if (!txMeta.history || txMeta.history.length === 0) { - const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta) - txMeta.history = [snapshot] + const { TransactionController } = newState + if (TransactionController && TransactionController.transactions) { + const transactions = newState.TransactionController.transactions + newState.TransactionController.transactions = transactions.map((txMeta) => { + // no history: initialize + if (!txMeta.history || txMeta.history.length === 0) { + const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta) + txMeta.history = [snapshot] + return txMeta + } + // has history: migrate + const newHistory = ( + txStateHistoryHelper.migrateFromSnapshotsToDiffs(txMeta.history) + // remove empty diffs + .filter((entry) => { + return !Array.isArray(entry) || entry.length > 0 + }) + ) + txMeta.history = newHistory return txMeta - } - // has history: migrate - const newHistory = ( - txStateHistoryHelper.migrateFromSnapshotsToDiffs(txMeta.history) - // remove empty diffs - .filter((entry) => { - return !Array.isArray(entry) || entry.length > 0 - }) - ) - txMeta.history = newHistory - return txMeta - }) + }) + } return newState } diff --git a/app/scripts/migrations/019.js b/app/scripts/migrations/019.js index 072c96370..ce5da6859 100644 --- a/app/scripts/migrations/019.js +++ b/app/scripts/migrations/019.js @@ -29,32 +29,36 @@ module.exports = { function transformState (state) { const newState = state - const transactions = newState.TransactionController.transactions + const { TransactionController } = newState + if (TransactionController && TransactionController.transactions) { - newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => { - if (txMeta.status !== 'submitted') return txMeta + const transactions = newState.TransactionController.transactions - const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed') - .filter((tx) => tx.txParams.from === txMeta.txParams.from) - .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from) - const highestConfirmedNonce = getHighestNonce(confirmedTxs) + newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => { + if (txMeta.status !== 'submitted') return txMeta - const pendingTxs = txList.filter((tx) => tx.status === 'submitted') - .filter((tx) => tx.txParams.from === txMeta.txParams.from) - .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from) - const highestContinuousNonce = getHighestContinuousFrom(pendingTxs, highestConfirmedNonce) + const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed') + .filter((tx) => tx.txParams.from === txMeta.txParams.from) + .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from) + const highestConfirmedNonce = getHighestNonce(confirmedTxs) - const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce) + const pendingTxs = txList.filter((tx) => tx.status === 'submitted') + .filter((tx) => tx.txParams.from === txMeta.txParams.from) + .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from) + const highestContinuousNonce = getHighestContinuousFrom(pendingTxs, highestConfirmedNonce) - if (parseInt(txMeta.txParams.nonce, 16) > maxNonce + 1) { - txMeta.status = 'failed' - txMeta.err = { - message: 'nonce too high', - note: 'migration 019 custom error', + const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce) + + if (parseInt(txMeta.txParams.nonce, 16) > maxNonce + 1) { + txMeta.status = 'failed' + txMeta.err = { + message: 'nonce too high', + note: 'migration 019 custom error', + } } - } - return txMeta - }) + return txMeta + }) + } return newState } diff --git a/app/scripts/migrations/022.js b/app/scripts/migrations/022.js index c3c0d53ef..1fbe241e6 100644 --- a/app/scripts/migrations/022.js +++ b/app/scripts/migrations/022.js @@ -28,12 +28,15 @@ module.exports = { function transformState (state) { const newState = state - const transactions = newState.TransactionController.transactions - - newState.TransactionController.transactions = transactions.map((txMeta) => { - if (txMeta.status !== 'submitted' || txMeta.submittedTime) return txMeta - txMeta.submittedTime = (new Date()).getTime() - return txMeta - }) + const { TransactionController } = newState + if (TransactionController && TransactionController.transactions) { + const transactions = newState.TransactionController.transactions + + newState.TransactionController.transactions = transactions.map((txMeta) => { + if (txMeta.status !== 'submitted' || txMeta.submittedTime) return txMeta + txMeta.submittedTime = (new Date()).getTime() + return txMeta + }) + } return newState } diff --git a/app/scripts/migrations/023.js b/app/scripts/migrations/023.js index bce0a5bea..151496b06 100644 --- a/app/scripts/migrations/023.js +++ b/app/scripts/migrations/023.js @@ -28,23 +28,27 @@ module.exports = { function transformState (state) { const newState = state - const transactions = newState.TransactionController.transactions - - if (transactions.length <= 40) return newState - - let reverseTxList = transactions.reverse() - let stripping = true - while (reverseTxList.length > 40 && stripping) { - let txIndex = reverseTxList.findIndex((txMeta) => { - return (txMeta.status === 'failed' || - txMeta.status === 'rejected' || - txMeta.status === 'confirmed' || - txMeta.status === 'dropped') - }) - if (txIndex < 0) stripping = false - else reverseTxList.splice(txIndex, 1) - } - newState.TransactionController.transactions = reverseTxList.reverse() + const { TransactionController } = newState + if (TransactionController && TransactionController.transactions) { + const transactions = newState.TransactionController.transactions + + if (transactions.length <= 40) return newState + + let reverseTxList = transactions.reverse() + let stripping = true + while (reverseTxList.length > 40 && stripping) { + let txIndex = reverseTxList.findIndex((txMeta) => { + return (txMeta.status === 'failed' || + txMeta.status === 'rejected' || + txMeta.status === 'confirmed' || + txMeta.status === 'dropped') + }) + if (txIndex < 0) stripping = false + else reverseTxList.splice(txIndex, 1) + } + + newState.TransactionController.transactions = reverseTxList.reverse() + } return newState } diff --git a/app/scripts/migrations/024.js b/app/scripts/migrations/024.js new file mode 100644 index 000000000..d0b276a79 --- /dev/null +++ b/app/scripts/migrations/024.js @@ -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 +} diff --git a/app/scripts/migrations/025.js b/app/scripts/migrations/025.js new file mode 100644 index 000000000..fc3b20a44 --- /dev/null +++ b/app/scripts/migrations/025.js @@ -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 +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index 811e06b6b..6c4a51b32 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -34,4 +34,6 @@ module.exports = [ require('./021'), require('./022'), require('./023'), + require('./024'), + require('./025'), ] diff --git a/app/scripts/migrations/template.js b/app/scripts/migrations/template.js new file mode 100644 index 000000000..0915c6bdf --- /dev/null +++ b/app/scripts/migrations/template.js @@ -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 +} |