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

feature/default_network_editable
Dan 7 years ago
commit f4d8da9277
  1. 14
      CHANGELOG.md
  2. 1
      app/_locales/index.json
  3. 99
      app/_locales/ja/messages.json
  4. 912
      app/_locales/tr/messages.json
  5. 2
      app/manifest.json
  6. 34
      app/scripts/background.js
  7. 3
      app/scripts/lib/is-popup-or-notification.js
  8. 6
      app/scripts/lib/tx-state-manager.js
  9. 7
      app/scripts/migrations/013.js
  10. 15
      app/scripts/migrations/015.js
  11. 22
      app/scripts/migrations/016.js
  12. 21
      app/scripts/migrations/017.js
  13. 39
      app/scripts/migrations/018.js
  14. 44
      app/scripts/migrations/019.js
  15. 17
      app/scripts/migrations/022.js
  16. 38
      app/scripts/migrations/023.js
  17. 21
      development/mock-dev.js
  18. 0
      development/states/tx-list-items.json
  19. 48
      docs/QA_Guide.md
  20. 151
      mascara/src/app/first-time/confirm-seed-screen.js
  21. 279
      mascara/src/app/first-time/create-password-screen.js
  22. 17
      mascara/src/app/first-time/import-seed-phrase-screen.js
  23. 187
      mascara/src/app/first-time/index.js
  24. 105
      mascara/src/app/first-time/notice-screen.js
  25. 147
      mascara/src/app/first-time/seed-screen.js
  26. 18
      mascara/src/app/first-time/unique-image-screen.js
  27. 98
      package-lock.json
  28. 8
      package.json
  29. 18
      test/integration/lib/confirm-sig-requests.js
  30. 3
      test/integration/lib/mascara-first-time.js
  31. 30
      test/integration/lib/send-new-ui.js
  32. 10
      test/unit/migrator-test.js
  33. 182
      ui/app/actions.js
  34. 930
      ui/app/app.js
  35. 54
      ui/app/components/account-menu/index.js
  36. 22
      ui/app/components/pages/add-token.js
  37. 34
      ui/app/components/pages/authenticated.js
  38. 0
      ui/app/components/pages/create-account/import-account/index.js
  39. 16
      ui/app/components/pages/create-account/import-account/json.js
  40. 21
      ui/app/components/pages/create-account/import-account/private-key.js
  41. 0
      ui/app/components/pages/create-account/import-account/seed.js
  42. 81
      ui/app/components/pages/create-account/index.js
  43. 30
      ui/app/components/pages/create-account/new-account.js
  44. 332
      ui/app/components/pages/home.js
  45. 25
      ui/app/components/pages/initialized.js
  46. 177
      ui/app/components/pages/keychains/restore-vault.js
  47. 195
      ui/app/components/pages/keychains/reveal-seed.js
  48. 28
      ui/app/components/pages/metamask-route.js
  49. 203
      ui/app/components/pages/notice.js
  50. 59
      ui/app/components/pages/settings/index.js
  51. 112
      ui/app/components/pages/settings/info.js
  52. 163
      ui/app/components/pages/settings/settings.js
  53. 193
      ui/app/components/pages/unlock.js
  54. 23
      ui/app/components/pending-tx/confirm-send-ether.js
  55. 22
      ui/app/components/pending-tx/confirm-send-token.js
  56. 8
      ui/app/components/send/send-v2-container.js
  57. 17
      ui/app/components/signature-request.js
  58. 26
      ui/app/components/tab-bar.js
  59. 18
      ui/app/components/tx-list.js
  60. 17
      ui/app/components/tx-view.js
  61. 18
      ui/app/components/wallet-view.js
  62. 74
      ui/app/conf-tx.js
  63. 6
      ui/app/css/index.scss
  64. 2
      ui/app/css/itcss/components/index.scss
  65. 5
      ui/app/css/itcss/components/new-account.scss
  66. 1
      ui/app/css/itcss/components/pages/index.scss
  67. 9
      ui/app/css/itcss/components/pages/unlock.scss
  68. 6
      ui/app/css/itcss/components/sections.scss
  69. 343
      ui/app/first-time/init-menu.js
  70. 7
      ui/app/i18n-provider.js
  71. 14
      ui/app/keychains/hd/recover-seed/confirmation.js
  72. 4
      ui/app/main-container.js
  73. 33
      ui/app/root.js
  74. 49
      ui/app/routes.js
  75. 10
      ui/app/select-app.js
  76. 27
      ui/app/send-v2.js
  77. 35
      ui/app/welcome-screen.js
  78. 1902
      yarn.lock

@ -1,9 +1,23 @@
# Changelog # Changelog
## Current Master ## Current Master
- 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 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 Download State Logs button [#3791](https://github.com/MetaMask/metamask-extension/issues/3791)
- Fix migration error reporting
## 4.5.3 Wed Apr 04 2018 ## 4.5.3 Wed Apr 04 2018

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

@ -20,6 +20,9 @@
"addToken": { "addToken": {
"message": "トークンを追加" "message": "トークンを追加"
}, },
"addTokens": {
"message": "トークンを追加"
},
"amount": { "amount": {
"message": "金額" "message": "金額"
}, },
@ -46,6 +49,9 @@
"balance": { "balance": {
"message": "残高:" "message": "残高:"
}, },
"balances": {
"message": "トークン残高"
},
"balanceIsInsufficientGas": { "balanceIsInsufficientGas": {
"message": "現在のガス総量に対して残高が不足しています" "message": "現在のガス総量に対して残高が不足しています"
}, },
@ -63,10 +69,10 @@
"message": "購入" "message": "購入"
}, },
"buyCoinbase": { "buyCoinbase": {
"message": "Coinbaseで購入" "message": "Coinbaseのサイトで購入"
}, },
"buyCoinbaseExplainer": { "buyCoinbaseExplainer": {
"message": "Coinbaseは、世界的なBitcoin、Ethereum、そしてLitecoinの取引所です。" "message": "Etherを購入できます。Coinbaseは、世界的なBitcoin、Ethereum、そしてLitecoinの取引所です。"
}, },
"cancel": { "cancel": {
"message": "キャンセル" "message": "キャンセル"
@ -90,7 +96,7 @@
"message": "トランザクションの確認" "message": "トランザクションの確認"
}, },
"continueToCoinbase": { "continueToCoinbase": {
"message": "Coinbaseで続行" "message": "Coinbaseを開く"
}, },
"contractDeployment": { "contractDeployment": {
"message": "コントラクトのデプロイ" "message": "コントラクトのデプロイ"
@ -138,6 +144,9 @@
"customGas": { "customGas": {
"message": "ガスのカスタマイズ" "message": "ガスのカスタマイズ"
}, },
"customToken": {
"message": "カスタムトークン"
},
"customize": { "customize": {
"message": "カスタマイズ" "message": "カスタマイズ"
}, },
@ -154,20 +163,20 @@
"message": "DENとは、あなたのパスワードが暗号化されたMetaMask内のストレージです。" "message": "DENとは、あなたのパスワードが暗号化されたMetaMask内のストレージです。"
}, },
"deposit": { "deposit": {
"message": "受取り" "message": "振込"
}, },
"depositBTC": { "depositBTC": {
"message": "あなたのBTCを次のアドレスへデポジット:" "message": "BTCを下記のアドレスへ振込んでください:"
}, },
"depositCoin": { "depositCoin": {
"message": "あなたの $1を次のアドレスへデポジット", "message": "$1を下記のアドレスへ振込んでください",
"description": "Tells the user what coin they have selected to deposit with shapeshift" "description": "Tells the user what coin they have selected to deposit with shapeshift"
}, },
"depositEth": { "depositEth": {
"message": "ETHをデポジット" "message": "ETHを入金"
}, },
"depositEther": { "depositEther": {
"message": "Etherをデポジット" "message": "Etherを振込"
}, },
"depositFiat": { "depositFiat": {
"message": "法定通貨でデポジット" "message": "法定通貨でデポジット"
@ -176,10 +185,10 @@
"message": "別のアカウントから入金" "message": "別のアカウントから入金"
}, },
"depositShapeShift": { "depositShapeShift": {
"message": "ShapeShiftで入金" "message": "ShapeShiftで交換"
}, },
"depositShapeShiftExplainer": { "depositShapeShiftExplainer": {
"message": "他の暗号通貨をEtherと交換してMetaMaskのウォレットへ入金できます。アカウント作成は不要です。" "message": "他の暗号通貨とEtherを交換して、MetaMaskのウォレットへ入金できます。アカウント作成は不要です。"
}, },
"details": { "details": {
"message": "詳細" "message": "詳細"
@ -188,10 +197,10 @@
"message": "ダイレクトデポジット" "message": "ダイレクトデポジット"
}, },
"directDepositEther": { "directDepositEther": {
"message": "Etherを直接受け取り" "message": "Etherを直接入金"
}, },
"directDepositEtherExplainer": { "directDepositEtherExplainer": {
"message": "Etherをすでにお持ちなら、MetaMaskの新しいウォレットにEtherを送信することができます。" "message": "既にEtherをお持ちなら、MetaMaskの新しいウォレットにEtherを送信することができます。"
}, },
"done": { "done": {
"message": "完了" "message": "完了"
@ -209,7 +218,7 @@
"message": "パスワードを入力" "message": "パスワードを入力"
}, },
"etherscanView": { "etherscanView": {
"message": "Etherscanでアカウントを参照" "message": "Etherscanでアカウントを確認"
}, },
"exchangeRate": { "exchangeRate": {
"message": "交換レート" "message": "交換レート"
@ -266,7 +275,7 @@
"message": "必要ガスプライス" "message": "必要ガスプライス"
}, },
"getEther": { "getEther": {
"message": "Etherをゲット" "message": "Etherを取得する"
}, },
"getEtherFromFaucet": { "getEtherFromFaucet": {
"message": "フォーセットで $1のEtherを得ることができます。", "message": "フォーセットで $1のEtherを得ることができます。",
@ -287,7 +296,7 @@
"message": "トークンを隠す" "message": "トークンを隠す"
}, },
"hideTokenPrompt": { "hideTokenPrompt": {
"message": "トークンを隠しますか?" "message": "トークンを隠しますか?"
}, },
"howToDeposit": { "howToDeposit": {
"message": "どのようにEtherをデポジットしますか?" "message": "どのようにEtherをデポジットしますか?"
@ -300,7 +309,7 @@
"message": "アカウントのインポート" "message": "アカウントのインポート"
}, },
"importAccountMsg": { "importAccountMsg": {
"message":"追加したアカウントはMetaMaskのアカウントシードフレーズとは関連付けられません。インポートしたアカウントについての詳細は" "message":"追加したアカウントはMetaMaskのアカウントパスフレーズとは関連付けられません。インポートしたアカウントについての詳細は"
}, },
"importAnAccount": { "importAnAccount": {
"message": "アカウントをインポート" "message": "アカウントをインポート"
@ -334,13 +343,22 @@
"message": "JSONファイル", "message": "JSONファイル",
"description": "format for importing an account" "description": "format for importing an account"
}, },
"keepTrackTokens": {
"message": "MetaMaskアカウントで入手したトークンを検索できます。"
},
"kovan": { "kovan": {
"message": "Kovanテストネットワーク" "message": "Kovanテストネットワーク"
}, },
"learnMore": {
"message": "詳細"
},
"lessThanMax": { "lessThanMax": {
"message": " $1以下にして下さい。", "message": " $1以下にして下さい。",
"description": "helper for inputting hex as decimal input" "description": "helper for inputting hex as decimal input"
}, },
"likeToAddTokens": {
"message": "トークンを追加しますか?"
},
"limit": { "limit": {
"message": "リミット" "message": "リミット"
}, },
@ -371,8 +389,11 @@
"myAccounts": { "myAccounts": {
"message": "マイアカウント" "message": "マイアカウント"
}, },
"mustSelectOne": {
"message": "一つ以上のトークンを選択してください。"
},
"needEtherInWallet": { "needEtherInWallet": {
"message": "MetaMaskを使って分散型アプリケーションを使用するためには、このウォレットにEtherが必要です。" "message": "MetaMask分散型アプリケーションを使用するためには、このウォレットにEtherが必要です。"
}, },
"needImportFile": { "needImportFile": {
"message": "インポートするファイルを選択してください。", "message": "インポートするファイルを選択してください。",
@ -411,7 +432,7 @@
"message": "この名前にはアドレスが設定されていません。" "message": "この名前にはアドレスが設定されていません。"
}, },
"noDeposits": { "noDeposits": {
"message": "デポジットがありません。" "message": "振込みがありません。"
}, },
"noTransactionHistory": { "noTransactionHistory": {
"message": "トランザクション履歴がありません。" "message": "トランザクション履歴がありません。"
@ -445,10 +466,13 @@
"description": "For importing an account from a private key" "description": "For importing an account from a private key"
}, },
"pasteSeed": { "pasteSeed": {
"message": "シードをここにペーストして下さい!" "message": "パスフレーズをここにペーストして下さい!"
}, },
"pleaseReviewTransaction": { "pleaseReviewTransaction": {
"message": "トランザクションをレビューして下さい。" "message": "トランザクションを確認して下さい。"
},
"popularTokens": {
"message": "人気のトークン"
}, },
"privateKey": { "privateKey": {
"message": "秘密鍵", "message": "秘密鍵",
@ -470,13 +494,13 @@
"message": "もっと読む" "message": "もっと読む"
}, },
"receive": { "receive": {
"message": "受" "message": "受取"
}, },
"recipientAddress": { "recipientAddress": {
"message": "受取人アドレス" "message": "受取人アドレス"
}, },
"refundAddress": { "refundAddress": {
"message": "あなたの返金先アドレス" "message": "受取アドレス"
}, },
"rejected": { "rejected": {
"message": "拒否されました" "message": "拒否されました"
@ -487,12 +511,18 @@
"restoreFromSeed": { "restoreFromSeed": {
"message": "パスフレーズから復元する" "message": "パスフレーズから復元する"
}, },
"restoreVault": {
"message": "ウォレットを復元する"
},
"required": { "required": {
"message": "必要です。" "message": "必要です。"
}, },
"retryWithMoreGas": { "retryWithMoreGas": {
"message": "より高いガスプライスで再度試して下さい。" "message": "より高いガスプライスで再度試して下さい。"
}, },
"walletSeed": {
"message": "ウォレットのパスフレーズ"
},
"revealSeedWords": { "revealSeedWords": {
"message": "パスフレーズを表示" "message": "パスフレーズを表示"
}, },
@ -519,23 +549,35 @@
"selectService": { "selectService": {
"message": "サービスを選択" "message": "サービスを選択"
}, },
"selectType": {
"message": "キーの種類"
},
"send": { "send": {
"message": "送信" "message": "送信"
}, },
"sendETH": {
"message": "ETHの送信"
},
"sendTokens": { "sendTokens": {
"message": "トークンを送る" "message": "トークンを送る"
}, },
"onlySendToEtherAddress": { "onlySendToEtherAddress": {
"message": "ETHはイーサリウムアカウントのみに送信できます。" "message": "ETHはイーサリウムアカウントのみに送信できます。"
}, },
"searchTokens": {
"message": "トークンの検索"
},
"sendTokensAnywhere": { "sendTokensAnywhere": {
"message": "イーサリアムアカウントを持っている人にトークンを送る" "message": "イーサリアムアカウントを持っている人にトークンを送る"
}, },
"settings": { "settings": {
"message": "設定" "message": "設定"
}, },
"info": {
"message": "情報"
},
"shapeshiftBuy": { "shapeshiftBuy": {
"message": "Shapeshiftで買う" "message": "Shapeshiftで交換"
}, },
"showPrivateKeys": { "showPrivateKeys": {
"message": "秘密鍵を表示" "message": "秘密鍵を表示"
@ -565,13 +607,13 @@
"message": "送信" "message": "送信"
}, },
"takesTooLong": { "takesTooLong": {
"message": "長くかかりすぎていますか?" "message": "送信に時間がかかりますか?"
}, },
"testFaucet": { "testFaucet": {
"message": "Faucetをテスト" "message": "Faucetをテスト"
}, },
"to": { "to": {
"message": "先" "message": "送信先"
}, },
"toETHviaShapeShift": { "toETHviaShapeShift": {
"message": "ShapeShiftで $1をETHにする", "message": "ShapeShiftで $1をETHにする",
@ -595,6 +637,9 @@
"total": { "total": {
"message": "合計" "message": "合計"
}, },
"transactions": {
"message": "トランザクション"
},
"transactionMemo": { "transactionMemo": {
"message": "トランザクションメモ (オプション)" "message": "トランザクションメモ (オプション)"
}, },
@ -609,7 +654,7 @@
"description": "Followed by a link (here) to view token balances" "description": "Followed by a link (here) to view token balances"
}, },
"typePassword": { "typePassword": {
"message": "パスワードタイプ" "message": "パスワードの入力"
}, },
"uiWelcome": { "uiWelcome": {
"message": "新UIへようこそ!(ベータ版)" "message": "新UIへようこそ!(ベータ版)"
@ -646,7 +691,7 @@
"message": "警告" "message": "警告"
}, },
"whatsThis": { "whatsThis": {
"message": "これは何でしょう?" "message": "この機能について"
}, },
"yourSigRequested": { "yourSigRequested": {
"message": "あなたの署名がリクエストされています。" "message": "あなたの署名がリクエストされています。"

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

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

@ -78,6 +78,28 @@ async function loadStateFromPersistence () {
diskStore.getState() || diskStore.getState() ||
migrator.generateInitialState(firstTimeState) 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 // report migration errors to sentry
migrator.on('error', (err) => { migrator.on('error', (err) => {
// get vault structure without secrets // get vault structure without secrets
@ -140,9 +162,9 @@ function setupController (initState, initLangCode) {
asStream(controller.store), asStream(controller.store),
debounce(1000), debounce(1000),
storeTransform(versionifyData), storeTransform(versionifyData),
storeTransform(syncDataWithExtension), storeTransform(persistData),
(error) => { (error) => {
log.error('pump hit error', error) log.error('MetaMask - Persistence pipeline failed', error)
} }
) )
@ -151,7 +173,13 @@ function setupController (initState, initLangCode) {
return versionedData 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) { if (localStore.isSupported) {
localStore.set(state) localStore.set(state)
.catch((err) => { .catch((err) => {

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

@ -92,8 +92,10 @@ module.exports = class TransactionStateManager extends EventEmitter {
// or rejected tx's. // or rejected tx's.
// not tx's that are pending or unapproved // not tx's that are pending or unapproved
if (txCount > txHistoryLimit - 1) { if (txCount > txHistoryLimit - 1) {
const index = transactions.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected') let index = transactions.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected')
transactions.splice(index, 1) if (index !== -1) {
transactions.splice(index, 1)
}
} }
transactions.push(txMeta) transactions.push(txMeta)
this._saveTxList(transactions) this._saveTxList(transactions)

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

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

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

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

@ -29,24 +29,27 @@ module.exports = {
function transformState (state) { function transformState (state) {
const newState = state const newState = state
const transactions = newState.TransactionController.transactions const { TransactionController } = newState
newState.TransactionController.transactions = transactions.map((txMeta) => { if (TransactionController && TransactionController.transactions) {
// no history: initialize const transactions = newState.TransactionController.transactions
if (!txMeta.history || txMeta.history.length === 0) { newState.TransactionController.transactions = transactions.map((txMeta) => {
const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta) // no history: initialize
txMeta.history = [snapshot] 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 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 return newState
} }

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

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

@ -28,23 +28,27 @@ module.exports = {
function transformState (state) { function transformState (state) {
const newState = 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 return newState
} }

@ -36,15 +36,28 @@ log.setLevel('debug')
// //
const qs = require('qs') const qs = require('qs')
let queryString = qs.parse(window.location.href.split('#')[1]) const routerPath = window.location.href.split('#')[1]
let selectedView = queryString.view || 'first time' let queryString = {}
let selectedView
if (routerPath) {
queryString = qs.parse(routerPath.split('?')[1])
}
selectedView = queryString.view || 'first time'
const firstState = states[selectedView] const firstState = states[selectedView]
updateQueryParams(selectedView) updateQueryParams(selectedView)
function updateQueryParams(newView) { function updateQueryParams (newView) {
queryString.view = newView queryString.view = newView
const params = qs.stringify(queryString) 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
}
} }
// //

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

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

@ -1,20 +1,26 @@
import EventEmitter from 'events'
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import {connect} from 'react-redux' import {connect} from 'react-redux'
import classnames from 'classnames' import { withRouter } from 'react-router-dom'
import {createNewVaultAndKeychain} from '../../../../ui/app/actions' import { compose } from 'recompose'
import LoadingScreen from './loading-screen' import { createNewVaultAndKeychain } from '../../../../ui/app/actions'
import Breadcrumbs from './breadcrumbs' import Breadcrumbs from './breadcrumbs'
import EventEmitter from 'events'
import Mascot from '../../../../ui/app/components/mascot' 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 { class CreatePasswordScreen extends Component {
static propTypes = { static propTypes = {
isLoading: PropTypes.bool.isRequired, isLoading: PropTypes.bool.isRequired,
createAccount: PropTypes.func.isRequired, createAccount: PropTypes.func.isRequired,
goToImportWithSeedPhrase: PropTypes.func.isRequired, history: PropTypes.object.isRequired,
goToImportAccount: PropTypes.func.isRequired, isInitialized: PropTypes.bool,
next: PropTypes.func.isRequired, isUnlocked: PropTypes.bool,
isMascara: PropTypes.bool.isRequired, isMascara: PropTypes.bool.isRequired,
} }
@ -23,13 +29,21 @@ class CreatePasswordScreen extends Component {
confirmPassword: '', confirmPassword: '',
} }
constructor () { constructor (props) {
super() super(props)
this.animationEventEmitter = new EventEmitter() this.animationEventEmitter = new EventEmitter()
} }
componentWillMount () {
const { isInitialized, history } = this.props
if (isInitialized) {
history.push(INITIALIZE_NOTICE_ROUTE)
}
}
isValid () { isValid () {
const {password, confirmPassword} = this.state const { password, confirmPassword } = this.state
if (!password || !confirmPassword) { if (!password || !confirmPassword) {
return false return false
@ -47,93 +61,182 @@ class CreatePasswordScreen extends Component {
return return
} }
const {password} = this.state const { password } = this.state
const {createAccount, next} = this.props const { createAccount, history } = this.props
this.setState({ isLoading: true })
createAccount(password) 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 () { render () {
const { isLoading, goToImportWithSeedPhrase, isMascara } = this.props const { history, isMascara } = this.props
return isLoading return (
? <LoadingScreen loadingMessage="Creating your new account" /> <div className={classnames({ 'first-view-main-wrapper': !isMascara })}>
: ( <div className={classnames({
<div className={classnames({ 'first-view-main-wrapper': !isMascara })}> 'first-view-main': !isMascara,
<div className={classnames({ 'first-view-main__mascara': isMascara,
'first-view-main': !isMascara, })}>
'first-view-main__mascara': isMascara, {isMascara && <div className="mascara-info first-view-phone-invisible">
})}> <Mascot
{isMascara && <div className="mascara-info first-view-phone-invisible"> animationEventEmitter={this.animationEventEmitter}
<Mascot width="225"
animationEventEmitter={this.animationEventEmitter} height="225"
width="225" />
height="225" <div className="info">
/> MetaMask is a secure identity vault for Ethereum.
<div className="info"> </div>
MetaMask is a secure identity vault for Ethereum. <div className="info">
</div> It allows you to hold ether & tokens, and interact with decentralized applications.
<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} />
</div> </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> </div>
) </div>
)
}
}
const mapStateToProps = ({ metamask, appState }) => {
const { isInitialized, isUnlocked, isMascara, noActiveNotices } = metamask
const { isLoading } = appState
return {
isLoading,
isInitialized,
isUnlocked,
isMascara,
noActiveNotices,
} }
} }
export default connect( export default compose(
({ appState: { isLoading }, metamask: { isMascara } }) => ({ isLoading, isMascara }), withRouter,
dispatch => ({ connect(
createAccount: password => dispatch(createNewVaultAndKeychain(password)), mapStateToProps,
}) dispatch => ({
createAccount: password => dispatch(createNewVaultAndKeychain(password)),
})
)
)(CreatePasswordScreen) )(CreatePasswordScreen)

@ -8,16 +8,16 @@ import {
displayWarning, displayWarning,
unMarkPasswordForgotten, unMarkPasswordForgotten,
} from '../../../../ui/app/actions' } from '../../../../ui/app/actions'
import { DEFAULT_ROUTE, INITIALIZE_NOTICE_ROUTE } from '../../../../ui/app/routes'
class ImportSeedPhraseScreen extends Component { class ImportSeedPhraseScreen extends Component {
static propTypes = { static propTypes = {
warning: PropTypes.string, warning: PropTypes.string,
back: PropTypes.func.isRequired,
next: PropTypes.func.isRequired,
createNewVaultAndRestore: PropTypes.func.isRequired, createNewVaultAndRestore: PropTypes.func.isRequired,
hideWarning: PropTypes.func.isRequired, hideWarning: PropTypes.func.isRequired,
displayWarning: PropTypes.func, displayWarning: PropTypes.func,
leaveImportSeedScreenState: PropTypes.func, leaveImportSeedScreenState: PropTypes.func,
history: PropTypes.object,
}; };
state = { state = {
@ -64,20 +64,21 @@ class ImportSeedPhraseScreen extends Component {
const { password, seedPhrase } = this.state const { password, seedPhrase } = this.state
const { const {
createNewVaultAndRestore, createNewVaultAndRestore,
next,
displayWarning, displayWarning,
leaveImportSeedScreenState, leaveImportSeedScreenState,
history,
} = this.props } = this.props
leaveImportSeedScreenState() leaveImportSeedScreenState()
createNewVaultAndRestore(password, this.parseSeedPhrase(seedPhrase)) createNewVaultAndRestore(password, this.parseSeedPhrase(seedPhrase))
.then(next) .then(() => history.push(INITIALIZE_NOTICE_ROUTE))
} }
render () { render () {
const { seedPhrase, password, confirmPassword } = this.state const { seedPhrase, password, confirmPassword } = this.state
const { warning } = this.props const { warning, isLoading } = this.props
const importDisabled = warning || !seedPhrase || !password || !confirmPassword const importDisabled = warning || !seedPhrase || !password || !confirmPassword || isLoading
return ( return (
<div className="first-view-main-wrapper"> <div className="first-view-main-wrapper">
<div className="first-view-main"> <div className="first-view-main">
@ -86,7 +87,7 @@ class ImportSeedPhraseScreen extends Component {
className="import-account__back-button" className="import-account__back-button"
onClick={e => { onClick={e => {
e.preventDefault() e.preventDefault()
this.props.back() this.props.history.goBack()
}} }}
href="#" href="#"
> >
@ -152,7 +153,7 @@ class ImportSeedPhraseScreen extends Component {
} }
export default connect( export default connect(
({ appState: { warning } }) => ({ warning }), ({ appState: { warning, isLoading } }) => ({ warning, isLoading }),
dispatch => ({ dispatch => ({
leaveImportSeedScreenState: () => { leaveImportSeedScreenState: () => {
dispatch(unMarkPasswordForgotten()) dispatch(unMarkPasswordForgotten())

@ -1,17 +1,26 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import {connect} from 'react-redux' import {connect} from 'react-redux'
import { withRouter, Switch, Route } from 'react-router-dom'
import { compose } from 'recompose'
import CreatePasswordScreen from './create-password-screen' import CreatePasswordScreen from './create-password-screen'
import UniqueImageScreen from './unique-image-screen' import UniqueImageScreen from './unique-image-screen'
import NoticeScreen from './notice-screen' import NoticeScreen from './notice-screen'
import BackupPhraseScreen from './backup-phrase-screen' import BackupPhraseScreen from './seed-screen'
import ImportAccountScreen from './import-account-screen' import ImportAccountScreen from './import-account-screen'
import ImportSeedPhraseScreen from './import-seed-phrase-screen' import ImportSeedPhraseScreen from './import-seed-phrase-screen'
import ConfirmSeed from './confirm-seed-screen'
import { import {
onboardingBuyEthView, INITIALIZE_ROUTE,
unMarkPasswordForgotten, INITIALIZE_IMPORT_ACCOUNT_ROUTE,
showModal, INITIALIZE_UNIQUE_IMAGE_ROUTE,
} from '../../../../ui/app/actions' 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 { class FirstTimeFlow extends Component {
@ -20,6 +29,10 @@ class FirstTimeFlow extends Component {
seedWords: PropTypes.string, seedWords: PropTypes.string,
address: PropTypes.string, address: PropTypes.string,
noActiveNotices: PropTypes.bool, noActiveNotices: PropTypes.bool,
goToBuyEtherView: PropTypes.func,
isUnlocked: PropTypes.bool,
history: PropTypes.object,
welcomeScreenSeen: PropTypes.bool,
}; };
static defaultProps = { static defaultProps = {
@ -28,145 +41,53 @@ class FirstTimeFlow extends Component {
noActiveNotices: false, 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 () { render () {
return ( return (
<div className="first-time-flow"> <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> </div>
) )
} }
} }
export default connect( const mapStateToProps = ({ metamask }) => {
({ const {
metamask: { isInitialized,
isInitialized, seedWords,
seedWords, noActiveNotices,
noActiveNotices, selectedAddress,
selectedAddress, forgottenPassword,
forgottenPassword, isMascara,
} isUnlocked,
}) => ({ welcomeScreenSeen,
} = metamask
return {
isMascara,
isInitialized, isInitialized,
seedWords, seedWords,
noActiveNotices, noActiveNotices,
address: selectedAddress, address: selectedAddress,
forgottenPassword, forgottenPassword,
}), isUnlocked,
dispatch => ({ welcomeScreenSeen,
leaveImportSeedScreenState: () => dispatch(unMarkPasswordForgotten()), }
openBuyEtherModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER'})), }
})
export default compose(
withRouter,
connect(mapStateToProps)
)(FirstTimeFlow) )(FirstTimeFlow)

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

@ -1,13 +1,13 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import {connect} from 'react-redux' import { connect } from 'react-redux'
import classnames from 'classnames' import classnames from 'classnames'
import shuffle from 'lodash.shuffle' import { withRouter } from 'react-router-dom'
import {compose, onlyUpdateForPropTypes} from 'recompose' import { compose } from 'recompose'
import Identicon from '../../../../ui/app/components/identicon' import Identicon from '../../../../ui/app/components/identicon'
import {confirmSeedWords} from '../../../../ui/app/actions'
import Breadcrumbs from './breadcrumbs' import Breadcrumbs from './breadcrumbs'
import LoadingScreen from './loading-screen' import LoadingScreen from './loading-screen'
import { DEFAULT_ROUTE, INITIALIZE_CONFIRM_SEED_ROUTE } from '../../../../ui/app/routes'
const LockIcon = props => ( const LockIcon = props => (
<svg <svg
@ -36,34 +36,32 @@ const LockIcon = props => (
/> />
</g> </g>
</svg> </svg>
); )
class BackupPhraseScreen extends Component { class BackupPhraseScreen extends Component {
static propTypes = { static propTypes = {
isLoading: PropTypes.bool.isRequired, isLoading: PropTypes.bool.isRequired,
address: PropTypes.string.isRequired, address: PropTypes.string.isRequired,
seedWords: PropTypes.string.isRequired, seedWords: PropTypes.string,
next: PropTypes.func.isRequired, history: PropTypes.object,
confirmSeedWords: PropTypes.func.isRequired,
}; };
static defaultProps = { static defaultProps = {
seedWords: '' seedWords: '',
}; }
static PAGE = {
SECRET: 'secret',
CONFIRM: 'confirm'
};
constructor(props) { constructor (props) {
const {seedWords} = props
super(props) super(props)
this.state = { this.state = {
isShowingSecret: false, 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 ( return (
<div className="backup-phrase__secret"> <div className="backup-phrase__secret">
<div className={classnames('backup-phrase__secret-words', { <div className={classnames('backup-phrase__secret-words', {
'backup-phrase__secret-words--hidden': !isShowingSecret 'backup-phrase__secret-words--hidden': !isShowingSecret,
})}> })}>
{this.props.seedWords} {this.props.seedWords}
</div> </div>
@ -96,6 +94,7 @@ class BackupPhraseScreen extends Component {
renderSecretScreen () { renderSecretScreen () {
const { isShowingSecret } = this.state const { isShowingSecret } = this.state
const { history } = this.props
return ( return (
<div className="backup-phrase__content-wrapper"> <div className="backup-phrase__content-wrapper">
@ -124,10 +123,7 @@ class BackupPhraseScreen extends Component {
<div className="backup-phrase__next-button"> <div className="backup-phrase__next-button">
<button <button
className="first-time-flow__button" className="first-time-flow__button"
onClick={() => isShowingSecret && this.setState({ onClick={() => isShowingSecret && history.push(INITIALIZE_CONFIRM_SEED_ROUTE)}
isShowingSecret: false,
page: BackupPhraseScreen.PAGE.CONFIRM,
})}
disabled={!isShowingSecret} disabled={!isShowingSecret}
> >
Next 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 () { render () {
return this.props.isLoading return this.props.isLoading
? <LoadingScreen loadingMessage="Creating your new account" /> ? <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-wrapper">
<div className="first-view-main"> <div className="first-view-main">
<div className="backup-phrase"> <div className="backup-phrase">
{this.renderBack()}
<Identicon address={this.props.address} diameter={70} /> <Identicon address={this.props.address} diameter={70} />
{this.renderContent()} {this.renderSecretScreen()}
</div> </div>
</div> </div>
</div> </div>
@ -249,15 +151,12 @@ class BackupPhraseScreen extends Component {
} }
export default compose( export default compose(
onlyUpdateForPropTypes, withRouter,
connect( connect(
({ metamask: { selectedAddress, seedWords }, appState: { isLoading } }) => ({ ({ metamask: { selectedAddress, seedWords }, appState: { isLoading } }) => ({
seedWords, seedWords,
isLoading, isLoading,
address: selectedAddress, address: selectedAddress,
}),
dispatch => ({
confirmSeedWords: () => dispatch(confirmSeedWords()),
}) })
) )
)(BackupPhraseScreen) )(BackupPhraseScreen)

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

98
package-lock.json generated

@ -5112,7 +5112,7 @@
"lodash": "4.17.4", "lodash": "4.17.4",
"object.assign": "4.1.0", "object.assign": "4.1.0",
"object.values": "1.0.4", "object.values": "1.0.4",
"prop-types": "15.6.0" "prop-types": "15.6.1"
} }
}, },
"enzyme-adapter-utils": { "enzyme-adapter-utils": {
@ -5123,7 +5123,7 @@
"requires": { "requires": {
"lodash": "4.17.4", "lodash": "4.17.4",
"object.assign": "4.1.0", "object.assign": "4.1.0",
"prop-types": "15.6.0" "prop-types": "15.6.1"
} }
}, },
"errno": { "errno": {
@ -5454,7 +5454,7 @@
"doctrine": "2.0.2", "doctrine": "2.0.2",
"has": "1.0.1", "has": "1.0.1",
"jsx-ast-utils": "2.0.1", "jsx-ast-utils": "2.0.1",
"prop-types": "15.6.0" "prop-types": "15.6.1"
} }
}, },
"eslint-scope": { "eslint-scope": {
@ -10394,6 +10394,18 @@
"request": "2.83.0" "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": { "hmac-drbg": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@ -18886,9 +18898,9 @@
} }
}, },
"prop-types": { "prop-types": {
"version": "15.6.0", "version": "15.6.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz",
"integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=", "integrity": "sha512-4ec7bY1Y66LymSUOH/zARVYObB23AT2h8cf6e/O6ZALB/N0sqZFEx7rq6EYPX2MkOdKORuooI/H5k9TlR4q7kQ==",
"requires": { "requires": {
"fbjs": "0.8.16", "fbjs": "0.8.16",
"loose-envify": "1.3.1", "loose-envify": "1.3.1",
@ -19316,7 +19328,7 @@
"fbjs": "0.8.16", "fbjs": "0.8.16",
"loose-envify": "1.3.1", "loose-envify": "1.3.1",
"object-assign": "4.1.1", "object-assign": "4.1.1",
"prop-types": "15.6.0" "prop-types": "15.6.1"
} }
}, },
"react-addons-css-transition-group": { "react-addons-css-transition-group": {
@ -19335,7 +19347,7 @@
"chain-function": "1.0.0", "chain-function": "1.0.0",
"dom-helpers": "3.3.1", "dom-helpers": "3.3.1",
"loose-envify": "1.3.1", "loose-envify": "1.3.1",
"prop-types": "15.6.0", "prop-types": "15.6.1",
"warning": "3.0.0" "warning": "3.0.0"
} }
} }
@ -19355,7 +19367,7 @@
"fbjs": "0.8.16", "fbjs": "0.8.16",
"loose-envify": "1.3.1", "loose-envify": "1.3.1",
"object-assign": "4.1.1", "object-assign": "4.1.1",
"prop-types": "15.6.0" "prop-types": "15.6.1"
} }
}, },
"react-hyperscript": { "react-hyperscript": {
@ -19368,7 +19380,7 @@
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.1.2.tgz", "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.1.2.tgz",
"integrity": "sha512-uAfIE4XEfBNXqjqQvd31Eoo20UkVk0xHJpfgP8HRT8gLczaN4LEmB1e2d8CJ5ziEt4clWnsk/1+QhTN27iO/EA==", "integrity": "sha512-uAfIE4XEfBNXqjqQvd31Eoo20UkVk0xHJpfgP8HRT8gLczaN4LEmB1e2d8CJ5ziEt4clWnsk/1+QhTN27iO/EA==",
"requires": { "requires": {
"prop-types": "15.6.0" "prop-types": "15.6.1"
} }
}, },
"react-markdown": { "react-markdown": {
@ -19376,7 +19388,7 @@
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-3.1.4.tgz", "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-3.1.4.tgz",
"integrity": "sha512-i8WueytRXbYzyJ2GemIOTMRx/NigPo8r4m3R/KvWD7r+PxPyc9ke66cI3DR7MBRSS+nVG82VWEgRDE1VaZUCqA==", "integrity": "sha512-i8WueytRXbYzyJ2GemIOTMRx/NigPo8r4m3R/KvWD7r+PxPyc9ke66cI3DR7MBRSS+nVG82VWEgRDE1VaZUCqA==",
"requires": { "requires": {
"prop-types": "15.6.0", "prop-types": "15.6.1",
"remark-parse": "4.0.0", "remark-parse": "4.0.0",
"unified": "6.1.6", "unified": "6.1.6",
"unist-util-visit": "1.3.0", "unist-util-visit": "1.3.0",
@ -19389,7 +19401,7 @@
"integrity": "sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ==", "integrity": "sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ==",
"requires": { "requires": {
"performance-now": "0.2.0", "performance-now": "0.2.0",
"prop-types": "15.6.0", "prop-types": "15.6.1",
"raf": "3.4.0" "raf": "3.4.0"
}, },
"dependencies": { "dependencies": {
@ -19410,7 +19422,49 @@
"lodash": "4.17.4", "lodash": "4.17.4",
"lodash-es": "4.17.4", "lodash-es": "4.17.4",
"loose-envify": "1.3.1", "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": { "react-select": {
@ -19419,7 +19473,7 @@
"integrity": "sha512-c4CdxweEHN9ra85HGWjSjIMBlJ5c0fsIXOymLFZS5UbZEQCiJGHnZTVLTt6/wDh8RKQnxl85gHUwzhG5XZLcyw==", "integrity": "sha512-c4CdxweEHN9ra85HGWjSjIMBlJ5c0fsIXOymLFZS5UbZEQCiJGHnZTVLTt6/wDh8RKQnxl85gHUwzhG5XZLcyw==",
"requires": { "requires": {
"classnames": "2.2.5", "classnames": "2.2.5",
"prop-types": "15.6.0", "prop-types": "15.6.1",
"react-input-autosize": "2.1.2" "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", "resolved": "https://registry.npmjs.org/react-simple-file-input/-/react-simple-file-input-2.0.1.tgz",
"integrity": "sha1-Fa1P/Hj+sbiCZJrWsBwDPvJ1ceY=", "integrity": "sha1-Fa1P/Hj+sbiCZJrWsBwDPvJ1ceY=",
"requires": { "requires": {
"prop-types": "15.6.0" "prop-types": "15.6.1"
} }
}, },
"react-test-renderer": { "react-test-renderer": {
@ -19472,7 +19526,7 @@
"resolved": "https://registry.npmjs.org/react-toggle-button/-/react-toggle-button-2.2.0.tgz", "resolved": "https://registry.npmjs.org/react-toggle-button/-/react-toggle-button-2.2.0.tgz",
"integrity": "sha1-obkhQ6oN9BRkL8sUHwh59UW8Wok=", "integrity": "sha1-obkhQ6oN9BRkL8sUHwh59UW8Wok=",
"requires": { "requires": {
"prop-types": "15.6.0", "prop-types": "15.6.1",
"react-motion": "0.5.2" "react-motion": "0.5.2"
} }
}, },
@ -19499,7 +19553,7 @@
"classnames": "2.2.5", "classnames": "2.2.5",
"dom-helpers": "3.3.1", "dom-helpers": "3.3.1",
"loose-envify": "1.3.1", "loose-envify": "1.3.1",
"prop-types": "15.6.0", "prop-types": "15.6.1",
"warning": "3.0.0" "warning": "3.0.0"
} }
}, },
@ -20058,6 +20112,11 @@
"value-or-function": "3.0.0" "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": { "resolve-url": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@ -23736,6 +23795,11 @@
"spdx-expression-parse": "1.0.4" "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": { "value-or-function": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz",

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

@ -21,11 +21,22 @@ async function runConfirmSigRequestsTest(assert, done) {
selectState.val('confirm sig requests') selectState.val('confirm sig requests')
reactTriggerChange(selectState[0]) 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') let confirmSigHeadline = await queryAsync($, '.request-signature__headline')
assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested') 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') 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') let confirmSigSignButton = await queryAsync($, 'button.btn-primary--lg')
confirmSigSignButton[0].click() confirmSigSignButton[0].click()
@ -33,11 +44,8 @@ async function runConfirmSigRequestsTest(assert, done) {
confirmSigHeadline = await queryAsync($, '.request-signature__headline') confirmSigHeadline = await queryAsync($, '.request-signature__headline')
assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested') 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') 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 = await queryAsync($, 'button.btn-primary--lg')
confirmSigSignButton[0].click() confirmSigSignButton[0].click()

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

@ -53,7 +53,7 @@ async function runSendFlowTest(assert, done) {
assert.equal(sendFromDropdownList.children().length, 4, 'send from dropdown shows all accounts') assert.equal(sendFromDropdownList.children().length, 4, 'send from dropdown shows all accounts')
sendFromDropdownList.children()[1].click() 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') assert.equal(sendFromFieldItemAddress[0].textContent, 'Send Account 2', 'send from field dropdown changes account name')
let sendToFieldInput = await queryAsync($, '.send-v2__to-autocomplete__input') 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') const sendButtonInEdit = await queryAsync($, '.btn-primary--lg.page-container__footer-button')
assert.equal(sendButtonInEdit[0].textContent, 'Next', 'next button in edit rendered') 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('send new ui')
selectState.val('confirm new ui')
reactTriggerChange(selectState[0]) 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') const cancelButtonInEdit = await queryAsync($, '.btn-secondary--lg.page-container__footer-button')
console.log(`++++++++++++++++++++++++++++++++ txView[0]`, txView[0]); cancelButtonInEdit[0].click()
// 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')
// 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') // assert.ok(txView[0], 'Should return to the account details screen after confirming')
} }

@ -50,11 +50,19 @@ describe('Migrator', () => {
const migrator = new Migrator({ migrations: liveMigrations }) const migrator = new Migrator({ migrations: liveMigrations })
migrator.migrateData(firstTimeState) migrator.migrateData(firstTimeState)
.then((migratedData) => { .then((migratedData) => {
console.log(migratedData)
const last = liveMigrations.length - 1 const last = liveMigrations.length - 1
assert.equal(migratedData.meta.version, liveMigrations[last].version) assert.equal(migratedData.meta.version, liveMigrations[last].version)
done() done()
}).catch(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)
})
}) })

@ -349,7 +349,7 @@ function transitionBackward () {
} }
function confirmSeedWords () { function confirmSeedWords () {
return (dispatch) => { return dispatch => {
dispatch(actions.showLoadingIndication()) dispatch(actions.showLoadingIndication())
log.debug(`background.clearSeedWordCache`) log.debug(`background.clearSeedWordCache`)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -357,7 +357,7 @@ function confirmSeedWords () {
dispatch(actions.hideLoadingIndication()) dispatch(actions.hideLoadingIndication())
if (err) { if (err) {
dispatch(actions.displayWarning(err.message)) dispatch(actions.displayWarning(err.message))
reject(err) return reject(err)
} }
log.info('Seed word cache cleared. ' + account) log.info('Seed word cache cleared. ' + account)
@ -573,35 +573,47 @@ function signMsg (msgData) {
return (dispatch) => { return (dispatch) => {
dispatch(actions.showLoadingIndication()) dispatch(actions.showLoadingIndication())
log.debug(`actions calling background.signMessage`) return new Promise((resolve, reject) => {
background.signMessage(msgData, (err, newState) => { log.debug(`actions calling background.signMessage`)
log.debug('signMessage called back') background.signMessage(msgData, (err, newState) => {
dispatch(actions.updateMetamaskState(newState)) log.debug('signMessage called back')
dispatch(actions.hideLoadingIndication()) dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
if (err) log.error(err) if (err) {
if (err) return dispatch(actions.displayWarning(err.message)) 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) { function signPersonalMsg (msgData) {
log.debug('action - signPersonalMsg') log.debug('action - signPersonalMsg')
return (dispatch) => { return dispatch => {
dispatch(actions.showLoadingIndication()) dispatch(actions.showLoadingIndication())
log.debug(`actions calling background.signPersonalMessage`) return new Promise((resolve, reject) => {
background.signPersonalMessage(msgData, (err, newState) => { log.debug(`actions calling background.signPersonalMessage`)
log.debug('signPersonalMessage called back') background.signPersonalMessage(msgData, (err, newState) => {
dispatch(actions.updateMetamaskState(newState)) log.debug('signPersonalMessage called back')
dispatch(actions.hideLoadingIndication()) dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
if (err) log.error(err) if (err) {
if (err) return dispatch(actions.displayWarning(err.message)) log.error(err)
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.completedTx(msgData.metamaskId)) dispatch(actions.completedTx(msgData.metamaskId))
return resolve(msgData)
})
}) })
} }
} }
@ -611,16 +623,22 @@ function signTypedMsg (msgData) {
return (dispatch) => { return (dispatch) => {
dispatch(actions.showLoadingIndication()) dispatch(actions.showLoadingIndication())
log.debug(`actions calling background.signTypedMessage`) return new Promise((resolve, reject) => {
background.signTypedMessage(msgData, (err, newState) => { log.debug(`actions calling background.signTypedMessage`)
log.debug('signTypedMessage called back') background.signTypedMessage(msgData, (err, newState) => {
dispatch(actions.updateMetamaskState(newState)) log.debug('signTypedMessage called back')
dispatch(actions.hideLoadingIndication()) dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
if (err) log.error(err) if (err) {
if (err) return dispatch(actions.displayWarning(err.message)) log.error(err)
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.completedTx(msgData.metamaskId)) dispatch(actions.completedTx(msgData.metamaskId))
return resolve(msgData)
})
}) })
} }
} }
@ -800,17 +818,24 @@ function updateTransaction (txData) {
function updateAndApproveTx (txData) { function updateAndApproveTx (txData) {
log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData)) log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData))
return (dispatch) => { return (dispatch) => {
log.debug(`actions calling background.updateAndApproveTx.`) log.debug(`actions calling background.updateAndApproveTx`)
background.updateAndApproveTransaction(txData, (err) => {
dispatch(actions.hideLoadingIndication()) return new Promise((resolve, reject) => {
dispatch(actions.updateTransactionParams(txData.id, txData.txParams)) background.updateAndApproveTransaction(txData, err => {
dispatch(actions.clearSend()) dispatch(actions.hideLoadingIndication())
if (err) { dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
dispatch(actions.txError(err)) dispatch(actions.clearSend())
dispatch(actions.goHome())
return log.error(err.message) if (err) {
} dispatch(actions.txError(err))
dispatch(actions.completedTx(txData.id)) dispatch(actions.goHome())
log.error(err.message)
reject(err)
}
dispatch(actions.completedTx(txData.id))
resolve(txData)
})
}) })
} }
} }
@ -838,29 +863,77 @@ function txError (err) {
} }
function cancelMsg (msgData) { function cancelMsg (msgData) {
log.debug(`background.cancelMessage`) return dispatch => {
background.cancelMessage(msgData.id) dispatch(actions.showLoadingIndication())
return actions.completedTx(msgData.id)
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) { function cancelPersonalMsg (msgData) {
const id = msgData.id return dispatch => {
background.cancelPersonalMessage(id) dispatch(actions.showLoadingIndication())
return actions.completedTx(id)
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) { function cancelTypedMsg (msgData) {
const id = msgData.id return dispatch => {
background.cancelTypedMessage(id) dispatch(actions.showLoadingIndication())
return actions.completedTx(id)
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) { function cancelTx (txData) {
return (dispatch) => { return dispatch => {
log.debug(`background.cancelTransaction`) log.debug(`background.cancelTransaction`)
background.cancelTransaction(txData.id, () => { return new Promise((resolve, reject) => {
dispatch(actions.clearSend()) background.cancelTransaction(txData.id, () => {
dispatch(actions.completedTx(txData.id)) dispatch(actions.clearSend())
dispatch(actions.completedTx(txData.id))
resolve(txData)
})
}) })
} }
} }
@ -1251,12 +1324,13 @@ function markNoticeRead (notice) {
dispatch(actions.displayWarning(err)) dispatch(actions.displayWarning(err))
return reject(err) return reject(err)
} }
if (notice) { if (notice) {
dispatch(actions.showNotice(notice)) dispatch(actions.showNotice(notice))
resolve() resolve(true)
} else { } else {
dispatch(actions.clearNotices()) dispatch(actions.clearNotices())
resolve() resolve(false)
} }
}) })
}) })
@ -1775,7 +1849,7 @@ function forceUpdateMetamaskState (dispatch) {
} }
dispatch(actions.updateMetamaskState(newState)) dispatch(actions.updateMetamaskState(newState))
resolve() resolve(newState)
}) })
}) })
} }

@ -1,62 +1,421 @@
const inherits = require('util').inherits const { Component } = require('react')
const Component = require('react').Component const PropTypes = require('prop-types')
const connect = require('react-redux').connect const connect = require('react-redux').connect
const { Route, Switch, withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const h = require('react-hyperscript') const h = require('react-hyperscript')
const PropTypes = require('prop-types')
const actions = require('./actions') const actions = require('./actions')
const classnames = require('classnames') 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 // init
const OldUIInitializeMenuScreen = require('./first-time/init-menu') const InitializeScreen = require('../../mascara/src/app/first-time').default
const InitializeMenuScreen = MascaraFirstTime
const NewKeyChainScreen = require('./new-keychain')
const WelcomeScreen = require('./welcome-screen').default
// accounts // accounts
const MainContainer = require('./main-container')
const SendTransactionScreen2 = require('./components/send/send-v2-container') const SendTransactionScreen2 = require('./components/send/send-v2-container')
const ConfirmTxScreen = require('./conf-tx') const ConfirmTxScreen = require('./conf-tx')
// notice
const NoticeScreen = require('./components/notice')
const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
// slideout menu // slideout menu
const WalletView = require('./components/wallet-view') const WalletView = require('./components/wallet-view')
// other views // other views
const Settings = require('./settings') const Home = require('./components/pages/home')
const AddTokenScreen = require('./add-token') const Authenticated = require('./components/pages/authenticated')
const Import = require('./accounts/import') const Initialized = require('./components/pages/initialized')
const NewAccount = require('./accounts/new-account') 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 Loading = require('./components/loading')
const NetworkIndicator = require('./components/network') const NetworkIndicator = require('./components/network')
const Identicon = require('./components/identicon') 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 ReactCSSTransitionGroup = require('react-addons-css-transition-group')
const NetworkDropdown = require('./components/dropdowns/network-dropdown') const NetworkDropdown = require('./components/dropdowns/network-dropdown')
const AccountMenu = require('./components/account-menu') const AccountMenu = require('./components/account-menu')
const QrView = require('./components/qr-code')
// Global Modals // Global Modals
const Modal = require('./components/modals/index').Modal const Modal = require('./components/modals/index').Modal
App.contextTypes = { // Routes
t: PropTypes.func, 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(),
module.exports = connect(mapStateToProps, mapDispatchToProps)(App) // network dropdown
h(NetworkDropdown, {
provider,
frequentRpcList,
}, []),
h(AccountMenu),
inherits(App, Component) (isLoading || isLoadingNetwork) && h(Loading, {
function App () { Component.call(this) } 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,
}
function mapStateToProps (state) { function mapStateToProps (state) {
const { appState, metamask } = state
const {
networkDropdownOpen,
sidebarOpen,
isLoading,
loadingMessage,
} = appState
const { const {
identities, identities,
accounts, accounts,
@ -65,17 +424,23 @@ function mapStateToProps (state) {
isInitialized, isInitialized,
noActiveNotices, noActiveNotices,
seedWords, seedWords,
} = state.metamask unapprovedTxs,
lastUnreadNotice,
lostAccounts,
unapprovedMsgCount,
unapprovedPersonalMsgCount,
unapprovedTypedMessagesCount,
} = metamask
const selected = address || Object.keys(accounts)[0] const selected = address || Object.keys(accounts)[0]
return { return {
// state from plugin // state from plugin
networkDropdownOpen: state.appState.networkDropdownOpen, networkDropdownOpen,
sidebarOpen: state.appState.sidebarOpen, sidebarOpen,
isLoading: state.appState.isLoading, isLoading,
loadingMessage: state.appState.loadingMessage, loadingMessage,
noActiveNotices: state.metamask.noActiveNotices, noActiveNotices,
isInitialized: state.metamask.isInitialized, isInitialized,
isUnlocked: state.metamask.isUnlocked, isUnlocked: state.metamask.isUnlocked,
selectedAddress: state.metamask.selectedAddress, selectedAddress: state.metamask.selectedAddress,
currentView: state.appState.currentView, currentView: state.appState.currentView,
@ -85,14 +450,17 @@ function mapStateToProps (state) {
isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized), isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized),
isPopup: state.metamask.isPopup, isPopup: state.metamask.isPopup,
seedWords: state.metamask.seedWords, seedWords: state.metamask.seedWords,
unapprovedTxs: state.metamask.unapprovedTxs, unapprovedTxs,
unapprovedMsgs: state.metamask.unapprovedMsgs, unapprovedMsgs: state.metamask.unapprovedMsgs,
unapprovedMsgCount,
unapprovedPersonalMsgCount,
unapprovedTypedMessagesCount,
menuOpen: state.appState.menuOpen, menuOpen: state.appState.menuOpen,
network: state.metamask.network, network: state.metamask.network,
provider: state.metamask.provider, provider: state.metamask.provider,
forgottenPassword: state.metamask.forgottenPassword, forgottenPassword: state.appState.forgottenPassword,
lastUnreadNotice: state.metamask.lastUnreadNotice, lastUnreadNotice,
lostAccounts: state.metamask.lostAccounts, lostAccounts,
frequentRpcList: state.metamask.frequentRpcList || [], frequentRpcList: state.metamask.frequentRpcList || [],
currentCurrency: state.metamask.currentCurrency, currentCurrency: state.metamask.currentCurrency,
isMouseUser: state.appState.isMouseUser, isMouseUser: state.appState.isMouseUser,
@ -120,479 +488,11 @@ function mapDispatchToProps (dispatch, ownProps) {
} }
} }
App.prototype.componentWillMount = function () { App.contextTypes = {
if (!this.props.currentCurrency) { t: PropTypes.func,
this.props.setCurrentCurrencyToUSD()
}
}
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 () { module.exports = compose(
const { provider } = this.props withRouter,
const providerName = provider.type connect(mapStateToProps, mapDispatchToProps)
)(App)
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
}

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

@ -7,8 +7,8 @@ const connect = require('react-redux').connect
const R = require('ramda') const R = require('ramda')
const Fuse = require('fuse.js') const Fuse = require('fuse.js')
const contractMap = require('eth-contract-metadata') const contractMap = require('eth-contract-metadata')
const TokenBalance = require('./components/token-balance') const TokenBalance = require('../../components/token-balance')
const Identicon = require('./components/identicon') const Identicon = require('../../components/identicon')
const contractList = Object.entries(contractMap) const contractList = Object.entries(contractMap)
.map(([ _, tokenData]) => tokenData) .map(([ _, tokenData]) => tokenData)
.filter(tokenData => Boolean(tokenData.erc20)) .filter(tokenData => Boolean(tokenData.erc20))
@ -24,9 +24,10 @@ const fuse = new Fuse(contractList, {
{ name: 'symbol', weight: 0.5 }, { name: 'symbol', weight: 0.5 },
], ],
}) })
const actions = require('./actions') const actions = require('../../actions')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const { tokenInfoGetter } = require('./token-util') const { tokenInfoGetter } = require('../../token-util')
const { DEFAULT_ROUTE } = require('../../routes')
const emptyAddr = '0x0000000000000000000000000000000000000000' const emptyAddr = '0x0000000000000000000000000000000000000000'
@ -47,7 +48,6 @@ function mapStateToProps (state) {
function mapDispatchToProps (dispatch) { function mapDispatchToProps (dispatch) {
return { return {
goHome: () => dispatch(actions.goHome()),
addTokens: tokens => dispatch(actions.addTokens(tokens)), addTokens: tokens => dispatch(actions.addTokens(tokens)),
} }
} }
@ -296,7 +296,7 @@ AddTokenScreen.prototype.renderConfirmation = function () {
selectedTokens, selectedTokens,
} = this.state } = this.state
const { addTokens, goHome } = this.props const { addTokens, history } = this.props
const customToken = { const customToken = {
address, address,
@ -333,7 +333,7 @@ AddTokenScreen.prototype.renderConfirmation = function () {
onClick: () => this.setState({ isShowingConfirmation: false }), onClick: () => this.setState({ isShowingConfirmation: false }),
}, this.context.t('back')), }, this.context.t('back')),
h('button.btn-primary--lg', { h('button.btn-primary--lg', {
onClick: () => addTokens(tokens).then(goHome), onClick: () => addTokens(tokens).then(() => history.push(DEFAULT_ROUTE)),
}, this.context.t('addTokens')), }, this.context.t('addTokens')),
]), ]),
]) ])
@ -382,12 +382,12 @@ AddTokenScreen.prototype.render = function () {
isShowingConfirmation, isShowingConfirmation,
displayedTab, displayedTab,
} = this.state } = this.state
const { goHome } = this.props const { history } = this.props
return h('div.add-token', [ return h('div.add-token', [
h('div.add-token__header', [ h('div.add-token__header', [
h('div.add-token__header__cancel', { h('div.add-token__header__cancel', {
onClick: () => goHome(), onClick: () => history.push(DEFAULT_ROUTE),
}, [ }, [
h('i.fa.fa-angle-left.fa-lg'), h('i.fa.fa-angle-left.fa-lg'),
h('span', this.context.t('cancel')), h('span', this.context.t('cancel')),
@ -414,14 +414,14 @@ AddTokenScreen.prototype.render = function () {
]), ]),
]), ]),
//
isShowingConfirmation isShowingConfirmation
? this.renderConfirmation() ? this.renderConfirmation()
: this.renderTabs(), : this.renderTabs(),
!isShowingConfirmation && h('div.add-token__buttons', [ !isShowingConfirmation && h('div.add-token__buttons', [
h('button.btn-secondary--lg.add-token__cancel-button', { h('button.btn-secondary--lg.add-token__cancel-button', {
onClick: goHome, onClick: () => history.push(DEFAULT_ROUTE),
}, this.context.t('cancel')), }, this.context.t('cancel')),
h('button.btn-primary--lg.add-token__confirm-button', { h('button.btn-primary--lg.add-token__confirm-button', {
onClick: this.onNext, onClick: this.onNext,

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,16 +1,18 @@
const { Component } = require('react') const { Component } = require('react')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const PropTypes = require('prop-types') const PropTypes = require('prop-types')
const h = require('react-hyperscript') const h = require('react-hyperscript')
const connect = require('react-redux').connect const connect = require('react-redux').connect
const actions = require('./actions') const actions = require('../../../actions')
const infuraCurrencies = require('./infura-conversion.json') const infuraCurrencies = require('../../../infura-conversion.json')
const validUrl = require('valid-url') const validUrl = require('valid-url')
const { exportAsFile } = require('./util') const { exportAsFile } = require('../../../util')
const TabBar = require('./components/tab-bar') const SimpleDropdown = require('../../dropdowns/simple-dropdown')
const SimpleDropdown = require('./components/dropdowns/simple-dropdown')
const ToggleButton = require('react-toggle-button') const ToggleButton = require('react-toggle-button')
const locales = require('../../app/_locales/index.json') const { REVEAL_SEED_ROUTE } = require('../../../routes')
const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums const locales = require('../../../../../app/_locales/index.json')
const { OLD_UI_NETWORK_TYPE } = require('../../../../../app/scripts/config').enums
const getInfuraCurrencyOptions = () => { const getInfuraCurrencyOptions = () => {
const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => { const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {
@ -40,30 +42,11 @@ class Settings extends Component {
constructor (props) { constructor (props) {
super(props) super(props)
const { tab } = props
const activeTab = tab === 'info' ? 'info' : 'settings'
this.state = { this.state = {
activeTab,
newRpc: '', 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 () { renderBlockieOptIn () {
const { metamask: { useBlockie }, setUseBlockie } = this.props const { metamask: { useBlockie }, setUseBlockie } = this.props
@ -253,7 +236,7 @@ class Settings extends Component {
} }
renderSeedWords () { renderSeedWords () {
const { revealSeedConfirmation } = this.props const { history } = this.props
return ( return (
h('div.settings__content-row', [ h('div.settings__content-row', [
@ -261,9 +244,9 @@ class Settings extends Component {
h('div.settings__content-item', [ h('div.settings__content-item', [
h('div.settings__content-item-col', [ h('div.settings__content-item-col', [
h('button.btn-primary--lg.settings__button--red', { h('button.btn-primary--lg.settings__button--red', {
onClick (event) { onClick: event => {
event.preventDefault() event.preventDefault()
revealSeedConfirmation() history.push(REVEAL_SEED_ROUTE)
}, },
}, this.context.t('revealSeedWords')), }, this.context.t('revealSeedWords')),
]), ]),
@ -310,7 +293,7 @@ class Settings extends Component {
]) ])
} }
renderSettingsContent () { render () {
const { warning, isMascara } = this.props const { warning, isMascara } = this.props
return ( 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 = { Settings.propTypes = {
tab: PropTypes.string,
metamask: PropTypes.object, metamask: PropTypes.object,
setUseBlockie: PropTypes.func, setUseBlockie: PropTypes.func,
setCurrentCurrency: PropTypes.func, setCurrentCurrency: PropTypes.func,
@ -451,7 +323,7 @@ Settings.propTypes = {
setFeatureFlagToBeta: PropTypes.func, setFeatureFlagToBeta: PropTypes.func,
showResetAccountConfirmationModal: PropTypes.func, showResetAccountConfirmationModal: PropTypes.func,
warning: PropTypes.string, warning: PropTypes.string,
goHome: PropTypes.func, history: PropTypes.object,
isMascara: PropTypes.bool, isMascara: PropTypes.bool,
updateCurrentLocale: PropTypes.func, updateCurrentLocale: PropTypes.func,
currentLocale: PropTypes.string, currentLocale: PropTypes.string,
@ -469,7 +341,6 @@ const mapStateToProps = state => {
const mapDispatchToProps = dispatch => { const mapDispatchToProps = dispatch => {
return { return {
goHome: () => dispatch(actions.goHome()),
setCurrentCurrency: currency => dispatch(actions.setCurrentCurrency(currency)), setCurrentCurrency: currency => dispatch(actions.setCurrentCurrency(currency)),
setRpcTarget: newRpc => dispatch(actions.setRpcTarget(newRpc)), setRpcTarget: newRpc => dispatch(actions.setRpcTarget(newRpc)),
displayWarning: warning => dispatch(actions.displayWarning(warning)), displayWarning: warning => dispatch(actions.displayWarning(warning)),
@ -490,5 +361,7 @@ Settings.contextTypes = {
t: PropTypes.func, t: PropTypes.func,
} }
module.exports = connect(mapStateToProps, mapDispatchToProps)(Settings) module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(Settings)

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

@ -1,4 +1,6 @@
const Component = require('react').Component const Component = require('react').Component
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const PropTypes = require('prop-types') const PropTypes = require('prop-types')
const connect = require('react-redux').connect const connect = require('react-redux').connect
const h = require('react-hyperscript') const h = require('react-hyperscript')
@ -23,12 +25,16 @@ const SenderToRecipient = require('../sender-to-recipient')
const NetworkDisplay = require('../network-display') const NetworkDisplay = require('../network-display')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
ConfirmSendEther.contextTypes = { ConfirmSendEther.contextTypes = {
t: PropTypes.func, t: PropTypes.func,
} }
module.exports = connect(mapStateToProps, mapDispatchToProps)(ConfirmSendEther) module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(ConfirmSendEther)
function mapStateToProps (state) { function mapStateToProps (state) {
@ -72,7 +78,6 @@ function mapDispatchToProps (dispatch) {
errors: { to: null, amount: null }, errors: { to: null, amount: null },
editingTransactionId: id, editingTransactionId: id,
})) }))
dispatch(actions.showSendPage())
}, },
cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })), cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })),
showCustomizeGasModal: (txMeta, sendGasLimit, sendGasPrice, sendGasTotal) => { showCustomizeGasModal: (txMeta, sendGasLimit, sendGasPrice, sendGasTotal) => {
@ -270,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 () { ConfirmSendEther.prototype.render = function () {
const { const {
editTransaction,
currentCurrency, currentCurrency,
clearSend, clearSend,
conversionRate, conversionRate,
@ -328,7 +338,7 @@ ConfirmSendEther.prototype.render = function () {
h('.page-container__header', [ h('.page-container__header', [
h('.page-container__header-row', [ h('.page-container__header-row', [
h('span.page-container__back-button', { h('span.page-container__back-button', {
onClick: () => editTransaction(txMeta), onClick: () => this.editTransaction(txMeta),
style: { style: {
visibility: !txMeta.lastGasPrice ? 'initial' : 'hidden', visibility: !txMeta.lastGasPrice ? 'initial' : 'hidden',
}, },
@ -506,7 +516,9 @@ ConfirmSendEther.prototype.render = function () {
}, this.context.t('cancel')), }, this.context.t('cancel')),
// Accept Button // 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')),
]), ]),
]), ]),
]) ])
@ -544,6 +556,7 @@ ConfirmSendEther.prototype.cancel = function (event, txMeta) {
const { cancelTransaction } = this.props const { cancelTransaction } = this.props
cancelTransaction(txMeta) cancelTransaction(txMeta)
.then(() => this.props.history.push(DEFAULT_ROUTE))
} }
ConfirmSendEther.prototype.isBalanceSufficient = function (txMeta) { ConfirmSendEther.prototype.isBalanceSufficient = function (txMeta) {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -30,6 +30,7 @@ const {
getGasTotal, getGasTotal,
} = require('./components/send/send-utils') } = require('./components/send/send-utils')
const { isValidAddress } = require('./util') const { isValidAddress } = require('./util')
const { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } = require('./routes')
import PageContainer from './components/page-container/page-container.component' import PageContainer from './components/page-container/page-container.component'
import SendHeader from './components/send_/send-header/send-header.container' import SendHeader from './components/send_/send-header/send-header.container'
@ -186,6 +187,25 @@ SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) {
} }
} }
SendTransactionScreen.prototype.renderHeader = function () {
const { selectedToken, clearSend, history } = this.props
return h('div.page-container__header', [
h('div.page-container__title', selectedToken ? this.context.t('sendTokens') : this.context.t('sendETH')),
h('div.page-container__subtitle', this.context.t('onlySendToEtherAddress')),
h('div.page-container__header-close', {
onClick: () => {
clearSend()
history.push(DEFAULT_ROUTE)
},
}),
])
}
SendTransactionScreen.prototype.renderErrorMessage = function (errorType) { SendTransactionScreen.prototype.renderErrorMessage = function (errorType) {
const { errors } = this.props const { errors } = this.props
const errorMessage = errors[errorType] const errorMessage = errors[errorType]
@ -478,12 +498,12 @@ SendTransactionScreen.prototype.renderForm = function () {
SendTransactionScreen.prototype.renderFooter = function () { SendTransactionScreen.prototype.renderFooter = function () {
const { const {
goHome,
clearSend, clearSend,
gasTotal, gasTotal,
tokenBalance, tokenBalance,
selectedToken, selectedToken,
errors: { amount: amountError, to: toError }, errors: { amount: amountError, to: toError },
history,
} = this.props } = this.props
const missingTokenBalance = selectedToken && !tokenBalance const missingTokenBalance = selectedToken && !tokenBalance
@ -492,7 +512,7 @@ SendTransactionScreen.prototype.renderFooter = function () {
return h(PageContainerFooter, { return h(PageContainerFooter, {
onCancel: () => { onCancel: () => {
clearSend() clearSend()
goHome() history.push(DEFAULT_ROUTE)
}, },
onSubmit: e => this.onSubmit(e), onSubmit: e => this.onSubmit(e),
disabled: !noErrors || !gasTotal || missingTokenBalance, disabled: !noErrors || !gasTotal || missingTokenBalance,
@ -600,7 +620,6 @@ SendTransactionScreen.prototype.onSubmit = function (event) {
if (editingTransactionId) { if (editingTransactionId) {
const editedTx = this.getEditedTx() const editedTx = this.getEditedTx()
updateTx(editedTx) updateTx(editedTx)
} else { } else {
@ -624,4 +643,6 @@ SendTransactionScreen.prototype.onSubmit = function (event) {
? signTokenTx(selectedToken.address, to, amount, txParams) ? signTokenTx(selectedToken.address, to, amount, txParams)
: signTx(txParams) : signTx(txParams)
} }
this.props.history.push(CONFIRM_TRANSACTION_ROUTE)
} }

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

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save