mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
commit
4598554fea
@ -30,6 +30,15 @@ workflows:
|
|||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
- prep-deps-firefox
|
- prep-deps-firefox
|
||||||
- prep-build
|
- prep-build
|
||||||
|
- test-e2e-beta-chrome:
|
||||||
|
requires:
|
||||||
|
- prep-deps-npm
|
||||||
|
- prep-build
|
||||||
|
- test-e2e-beta-firefox:
|
||||||
|
requires:
|
||||||
|
- prep-deps-npm
|
||||||
|
- prep-deps-firefox
|
||||||
|
- prep-build
|
||||||
- test-unit:
|
- test-unit:
|
||||||
requires:
|
requires:
|
||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
@ -57,6 +66,8 @@ workflows:
|
|||||||
- test-unit
|
- test-unit
|
||||||
- test-e2e-chrome
|
- test-e2e-chrome
|
||||||
- test-e2e-firefox
|
- test-e2e-firefox
|
||||||
|
- test-e2e-beta-chrome
|
||||||
|
- test-e2e-beta-firefox
|
||||||
- test-integration-mascara-chrome
|
- test-integration-mascara-chrome
|
||||||
- test-integration-mascara-firefox
|
- test-integration-mascara-firefox
|
||||||
- test-integration-flat-chrome
|
- test-integration-flat-chrome
|
||||||
@ -110,9 +121,7 @@ jobs:
|
|||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
name: Download Firefox
|
name: Download Firefox
|
||||||
command: >
|
command: ./.circleci/scripts/firefox-download.sh
|
||||||
wget https://ftp.mozilla.org/pub/firefox/releases/58.0/linux-x86_64/en-US/firefox-58.0.tar.bz2
|
|
||||||
&& tar xjf firefox-58.0.tar.bz2
|
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: dependency-cache-firefox-{{ .Revision }}
|
key: dependency-cache-firefox-{{ .Revision }}
|
||||||
paths:
|
paths:
|
||||||
@ -203,15 +212,13 @@ jobs:
|
|||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: build-cache-{{ .Revision }}
|
key: build-cache-{{ .Revision }}
|
||||||
- run:
|
- run:
|
||||||
name: Test
|
name: test:e2e:chrome
|
||||||
command: npm run test:e2e:chrome
|
command: npm run test:e2e:chrome
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: test-artifacts
|
path: test-artifacts
|
||||||
destination: test-artifacts
|
destination: test-artifacts
|
||||||
|
|
||||||
test-e2e-firefox:
|
test-e2e-firefox:
|
||||||
environment:
|
|
||||||
browsers: '["Firefox"]'
|
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:8-browsers
|
- image: circleci/node:8-browsers
|
||||||
steps:
|
steps:
|
||||||
@ -220,11 +227,7 @@ jobs:
|
|||||||
key: dependency-cache-firefox-{{ .Revision }}
|
key: dependency-cache-firefox-{{ .Revision }}
|
||||||
- run:
|
- run:
|
||||||
name: Install firefox
|
name: Install firefox
|
||||||
command: >
|
command: ./.circleci/scripts/firefox-install.sh
|
||||||
sudo rm -r /opt/firefox
|
|
||||||
&& sudo mv firefox /opt/firefox58
|
|
||||||
&& sudo mv /usr/bin/firefox /usr/bin/firefox-old
|
|
||||||
&& sudo ln -s /opt/firefox58/firefox /usr/bin/firefox
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: dependency-cache-{{ .Revision }}
|
key: dependency-cache-{{ .Revision }}
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
@ -236,6 +239,43 @@ jobs:
|
|||||||
path: test-artifacts
|
path: test-artifacts
|
||||||
destination: test-artifacts
|
destination: test-artifacts
|
||||||
|
|
||||||
|
test-e2e-beta-chrome:
|
||||||
|
docker:
|
||||||
|
- image: circleci/node:8-browsers
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: dependency-cache-{{ .Revision }}
|
||||||
|
- restore_cache:
|
||||||
|
key: build-cache-{{ .Revision }}
|
||||||
|
- run:
|
||||||
|
name: test:e2e:chrome:beta
|
||||||
|
command: npm run test:e2e:chrome:beta
|
||||||
|
- store_artifacts:
|
||||||
|
path: test-artifacts
|
||||||
|
destination: test-artifacts
|
||||||
|
|
||||||
|
test-e2e-beta-firefox:
|
||||||
|
docker:
|
||||||
|
- image: circleci/node:8-browsers
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: dependency-cache-firefox-{{ .Revision }}
|
||||||
|
- run:
|
||||||
|
name: Install firefox
|
||||||
|
command: ./.circleci/scripts/firefox-install.sh
|
||||||
|
- restore_cache:
|
||||||
|
key: dependency-cache-{{ .Revision }}
|
||||||
|
- restore_cache:
|
||||||
|
key: build-cache-{{ .Revision }}
|
||||||
|
- run:
|
||||||
|
name: test:e2e:firefox:beta
|
||||||
|
command: npm run test:e2e:firefox:beta
|
||||||
|
- store_artifacts:
|
||||||
|
path: test-artifacts
|
||||||
|
destination: test-artifacts
|
||||||
|
|
||||||
job-screens:
|
job-screens:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:8-browsers
|
- image: circleci/node:8-browsers
|
||||||
@ -325,11 +365,7 @@ jobs:
|
|||||||
key: dependency-cache-firefox-{{ .Revision }}
|
key: dependency-cache-firefox-{{ .Revision }}
|
||||||
- run:
|
- run:
|
||||||
name: Install firefox
|
name: Install firefox
|
||||||
command: >
|
command: ./.circleci/scripts/firefox-install.sh
|
||||||
sudo rm -r /opt/firefox
|
|
||||||
&& sudo mv firefox /opt/firefox58
|
|
||||||
&& sudo mv /usr/bin/firefox /usr/bin/firefox-old
|
|
||||||
&& sudo ln -s /opt/firefox58/firefox /usr/bin/firefox
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: dependency-cache-{{ .Revision }}
|
key: dependency-cache-{{ .Revision }}
|
||||||
- run:
|
- run:
|
||||||
@ -372,11 +408,7 @@ jobs:
|
|||||||
key: dependency-cache-firefox-{{ .Revision }}
|
key: dependency-cache-firefox-{{ .Revision }}
|
||||||
- run:
|
- run:
|
||||||
name: Install firefox
|
name: Install firefox
|
||||||
command: >
|
command: ./.circleci/scripts/firefox-install.sh
|
||||||
sudo rm -r /opt/firefox
|
|
||||||
&& sudo mv firefox /opt/firefox58
|
|
||||||
&& sudo mv /usr/bin/firefox /usr/bin/firefox-old
|
|
||||||
&& sudo ln -s /opt/firefox58/firefox /usr/bin/firefox
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: dependency-cache-{{ .Revision }}
|
key: dependency-cache-{{ .Revision }}
|
||||||
- run:
|
- run:
|
||||||
@ -415,4 +447,3 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: All Tests Passed
|
name: All Tests Passed
|
||||||
command: echo 'weew - everything passed!'
|
command: echo 'weew - everything passed!'
|
||||||
|
|
||||||
|
6
.circleci/scripts/firefox-download.sh
Executable file
6
.circleci/scripts/firefox-download.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
echo "Downloading firefox..."
|
||||||
|
wget https://ftp.mozilla.org/pub/firefox/releases/58.0/linux-x86_64/en-US/firefox-58.0.tar.bz2 \
|
||||||
|
&& tar xjf firefox-58.0.tar.bz2
|
||||||
|
echo "firefox download complete"
|
8
.circleci/scripts/firefox-install.sh
Executable file
8
.circleci/scripts/firefox-install.sh
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
echo "Installing firefox..."
|
||||||
|
sudo rm -r /opt/firefox
|
||||||
|
sudo mv firefox /opt/firefox58
|
||||||
|
sudo mv /usr/bin/firefox /usr/bin/firefox-old
|
||||||
|
sudo ln -s /opt/firefox58/firefox /usr/bin/firefox
|
||||||
|
echo "Firefox installed."
|
@ -142,6 +142,7 @@
|
|||||||
"operator-linebreak": [1, "after", { "overrides": { "?": "ignore", ":": "ignore" } }],
|
"operator-linebreak": [1, "after", { "overrides": { "?": "ignore", ":": "ignore" } }],
|
||||||
"padded-blocks": "off",
|
"padded-blocks": "off",
|
||||||
"quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}],
|
"quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}],
|
||||||
|
"react/no-deprecated": 0,
|
||||||
"semi": [2, "never"],
|
"semi": [2, "never"],
|
||||||
"semi-spacing": [2, { "before": false, "after": true }],
|
"semi-spacing": [2, { "before": false, "after": true }],
|
||||||
"space-before-blocks": [1, "always"],
|
"space-before-blocks": [1, "always"],
|
||||||
|
11
CHANGELOG.md
11
CHANGELOG.md
@ -2,6 +2,17 @@
|
|||||||
|
|
||||||
## Current Master
|
## Current Master
|
||||||
|
|
||||||
|
## 4.8.0 Thur Jun 14 2018
|
||||||
|
|
||||||
|
- [#4513](https://github.com/MetaMask/metamask-extension/pull/4513): Attempting to import an empty private key will now show a clear error.
|
||||||
|
- [#4570](https://github.com/MetaMask/metamask-extension/pull/4570): Fix bug where metamask data would stop being written to disk after prolonged use.
|
||||||
|
- [#4523](https://github.com/MetaMask/metamask-extension/pull/4523): Fix bug where account reset did not work with custom RPC providers.
|
||||||
|
- [#4524](https://github.com/MetaMask/metamask-extension/pull/4524): Fix for Brave i18n getAcceptLanguages.
|
||||||
|
- [#4557](https://github.com/MetaMask/metamask-extension/pull/4557): Fix bug where nonce mutex was never released.
|
||||||
|
- [#4558](https://github.com/MetaMask/metamask-extension/pull/4558): Stop reloading browser page on Ethereum network change.
|
||||||
|
- [#4566](https://github.com/MetaMask/metamask-extension/pull/4566): Add phishing notice.
|
||||||
|
- [#4591](https://github.com/MetaMask/metamask-extension/pull/4591): Allow Copying Token Addresses and link to Token on Etherscan.
|
||||||
|
|
||||||
## 4.7.4 Tue Jun 05 2018
|
## 4.7.4 Tue Jun 05 2018
|
||||||
|
|
||||||
- Add diagnostic reporting for users with multiple HD keyrings
|
- Add diagnostic reporting for users with multiple HD keyrings
|
||||||
|
@ -146,6 +146,9 @@
|
|||||||
"copy": {
|
"copy": {
|
||||||
"message": "Copy"
|
"message": "Copy"
|
||||||
},
|
},
|
||||||
|
"copyContractAddress": {
|
||||||
|
"message": "Copy Contract Address"
|
||||||
|
},
|
||||||
"copyToClipboard": {
|
"copyToClipboard": {
|
||||||
"message": "Copy to clipboard"
|
"message": "Copy to clipboard"
|
||||||
},
|
},
|
||||||
@ -253,6 +256,9 @@
|
|||||||
"editAccountName": {
|
"editAccountName": {
|
||||||
"message": "Edit Account Name"
|
"message": "Edit Account Name"
|
||||||
},
|
},
|
||||||
|
"editingTransaction": {
|
||||||
|
"message": "Make changes to your transaction"
|
||||||
|
},
|
||||||
"emailUs": {
|
"emailUs": {
|
||||||
"message": "Email us!"
|
"message": "Email us!"
|
||||||
},
|
},
|
||||||
@ -771,6 +777,10 @@
|
|||||||
"onlySendToEtherAddress": {
|
"onlySendToEtherAddress": {
|
||||||
"message": "Only send ETH to an Ethereum address."
|
"message": "Only send ETH to an Ethereum address."
|
||||||
},
|
},
|
||||||
|
"onlySendTokensToAccountAddress": {
|
||||||
|
"message": "Only send $1 to an Ethereum account address.",
|
||||||
|
"description": "displays token symbol"
|
||||||
|
},
|
||||||
"searchTokens": {
|
"searchTokens": {
|
||||||
"message": "Search Tokens"
|
"message": "Search Tokens"
|
||||||
},
|
},
|
||||||
@ -948,6 +958,9 @@
|
|||||||
"viewAccount": {
|
"viewAccount": {
|
||||||
"message": "View Account"
|
"message": "View Account"
|
||||||
},
|
},
|
||||||
|
"viewOnEtherscan": {
|
||||||
|
"message": "View on Etherscan"
|
||||||
|
},
|
||||||
"visitWebSite": {
|
"visitWebSite": {
|
||||||
"message": "Visit our web site"
|
"message": "Visit our web site"
|
||||||
},
|
},
|
||||||
|
@ -62,6 +62,9 @@
|
|||||||
"message": " $1以上 $2以下にして下さい。",
|
"message": " $1以上 $2以下にして下さい。",
|
||||||
"description": "helper for inputting hex as decimal input"
|
"description": "helper for inputting hex as decimal input"
|
||||||
},
|
},
|
||||||
|
"blockiesIdenticon": {
|
||||||
|
"message": "Blockies Identicon を使用"
|
||||||
|
},
|
||||||
"borrowDharma": {
|
"borrowDharma": {
|
||||||
"message": "Dharmaで借りる(ベータ版)"
|
"message": "Dharmaで借りる(ベータ版)"
|
||||||
},
|
},
|
||||||
@ -95,6 +98,9 @@
|
|||||||
"confirmTransaction": {
|
"confirmTransaction": {
|
||||||
"message": "トランザクションの確認"
|
"message": "トランザクションの確認"
|
||||||
},
|
},
|
||||||
|
"continue": {
|
||||||
|
"message": "続行"
|
||||||
|
},
|
||||||
"continueToCoinbase": {
|
"continueToCoinbase": {
|
||||||
"message": "Coinbaseを開く"
|
"message": "Coinbaseを開く"
|
||||||
},
|
},
|
||||||
@ -359,6 +365,9 @@
|
|||||||
"likeToAddTokens": {
|
"likeToAddTokens": {
|
||||||
"message": "トークンを追加しますか?"
|
"message": "トークンを追加しますか?"
|
||||||
},
|
},
|
||||||
|
"links": {
|
||||||
|
"message": "リンク"
|
||||||
|
},
|
||||||
"limit": {
|
"limit": {
|
||||||
"message": "リミット"
|
"message": "リミット"
|
||||||
},
|
},
|
||||||
@ -371,12 +380,18 @@
|
|||||||
"localhost": {
|
"localhost": {
|
||||||
"message": "Localhost 8545"
|
"message": "Localhost 8545"
|
||||||
},
|
},
|
||||||
|
"login": {
|
||||||
|
"message": "ログイン"
|
||||||
|
},
|
||||||
"logout": {
|
"logout": {
|
||||||
"message": "ログアウト"
|
"message": "ログアウト"
|
||||||
},
|
},
|
||||||
"loose": {
|
"loose": {
|
||||||
"message": "外部秘密鍵"
|
"message": "外部秘密鍵"
|
||||||
},
|
},
|
||||||
|
"max": {
|
||||||
|
"message": "最大"
|
||||||
|
},
|
||||||
"mainnet": {
|
"mainnet": {
|
||||||
"message": "Ethereumメインネットワーク"
|
"message": "Ethereumメインネットワーク"
|
||||||
},
|
},
|
||||||
@ -417,7 +432,7 @@
|
|||||||
"message": "新規コントラクト"
|
"message": "新規コントラクト"
|
||||||
},
|
},
|
||||||
"newPassword": {
|
"newPassword": {
|
||||||
"message": "新規パスワード(最低8文字)"
|
"message": "新規パスワード(最低8文字)"
|
||||||
},
|
},
|
||||||
"newRecipient": {
|
"newRecipient": {
|
||||||
"message": "新規受取人"
|
"message": "新規受取人"
|
||||||
@ -453,6 +468,9 @@
|
|||||||
"message": "または",
|
"message": "または",
|
||||||
"description": "choice between creating or importing a new account"
|
"description": "choice between creating or importing a new account"
|
||||||
},
|
},
|
||||||
|
"password": {
|
||||||
|
"message": "パスワード"
|
||||||
|
},
|
||||||
"passwordMismatch": {
|
"passwordMismatch": {
|
||||||
"message": "パスワードが一致しません。",
|
"message": "パスワードが一致しません。",
|
||||||
"description": "in password creation process, the two new password fields did not match"
|
"description": "in password creation process, the two new password fields did not match"
|
||||||
@ -474,6 +492,9 @@
|
|||||||
"popularTokens": {
|
"popularTokens": {
|
||||||
"message": "人気のトークン"
|
"message": "人気のトークン"
|
||||||
},
|
},
|
||||||
|
"privacyMsg": {
|
||||||
|
"message": "プライバシーポリシー"
|
||||||
|
},
|
||||||
"privateKey": {
|
"privateKey": {
|
||||||
"message": "秘密鍵",
|
"message": "秘密鍵",
|
||||||
"description": "select this type of file to use to import an account"
|
"description": "select this type of file to use to import an account"
|
||||||
@ -546,6 +567,12 @@
|
|||||||
"message": "ファイルとして保存",
|
"message": "ファイルとして保存",
|
||||||
"description": "Account export process"
|
"description": "Account export process"
|
||||||
},
|
},
|
||||||
|
"search": {
|
||||||
|
"message": "検索"
|
||||||
|
},
|
||||||
|
"searchResults": {
|
||||||
|
"message": "検索結果"
|
||||||
|
},
|
||||||
"selectService": {
|
"selectService": {
|
||||||
"message": "サービスを選択"
|
"message": "サービスを選択"
|
||||||
},
|
},
|
||||||
@ -575,7 +602,7 @@
|
|||||||
},
|
},
|
||||||
"info": {
|
"info": {
|
||||||
"message": "情報"
|
"message": "情報"
|
||||||
},
|
},
|
||||||
"shapeshiftBuy": {
|
"shapeshiftBuy": {
|
||||||
"message": "Shapeshiftで交換"
|
"message": "Shapeshiftで交換"
|
||||||
},
|
},
|
||||||
@ -609,6 +636,9 @@
|
|||||||
"takesTooLong": {
|
"takesTooLong": {
|
||||||
"message": "送信に時間がかかりますか?"
|
"message": "送信に時間がかかりますか?"
|
||||||
},
|
},
|
||||||
|
"terms": {
|
||||||
|
"message": "利用規約"
|
||||||
|
},
|
||||||
"testFaucet": {
|
"testFaucet": {
|
||||||
"message": "Faucetをテスト"
|
"message": "Faucetをテスト"
|
||||||
},
|
},
|
||||||
@ -619,6 +649,9 @@
|
|||||||
"message": "ShapeShiftで $1をETHにする",
|
"message": "ShapeShiftで $1をETHにする",
|
||||||
"description": "system will fill in deposit type in start of message"
|
"description": "system will fill in deposit type in start of message"
|
||||||
},
|
},
|
||||||
|
"token": {
|
||||||
|
"message": "トークン"
|
||||||
|
},
|
||||||
"tokenAddress": {
|
"tokenAddress": {
|
||||||
"message": "トークンアドレス"
|
"message": "トークンアドレス"
|
||||||
},
|
},
|
||||||
@ -690,6 +723,12 @@
|
|||||||
"warning": {
|
"warning": {
|
||||||
"message": "警告"
|
"message": "警告"
|
||||||
},
|
},
|
||||||
|
"welcomeBack": {
|
||||||
|
"message": "おかえりなさい!"
|
||||||
|
},
|
||||||
|
"welcomeBeta": {
|
||||||
|
"message": "MetaMask ベータ版へようこそ!"
|
||||||
|
},
|
||||||
"whatsThis": {
|
"whatsThis": {
|
||||||
"message": "この機能について"
|
"message": "この機能について"
|
||||||
},
|
},
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "__MSG_appName__",
|
"name": "__MSG_appName__",
|
||||||
"short_name": "__MSG_appName__",
|
"short_name": "__MSG_appName__",
|
||||||
"version": "4.7.4",
|
"version": "4.8.0",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"author": "https://metamask.io",
|
"author": "https://metamask.io",
|
||||||
"description": "__MSG_appDescription__",
|
"description": "__MSG_appDescription__",
|
||||||
|
@ -16,7 +16,18 @@ const accountImporter = {
|
|||||||
|
|
||||||
strategies: {
|
strategies: {
|
||||||
'Private Key': (privateKey) => {
|
'Private Key': (privateKey) => {
|
||||||
const stripped = ethUtil.stripHexPrefix(privateKey)
|
if (!privateKey) {
|
||||||
|
throw new Error('Cannot import an empty key.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefixed = ethUtil.addHexPrefix(privateKey)
|
||||||
|
const buffer = ethUtil.toBuffer(prefixed)
|
||||||
|
|
||||||
|
if (!ethUtil.isValidPrivate(buffer)) {
|
||||||
|
throw new Error('Cannot import invalid private key.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const stripped = ethUtil.stripHexPrefix(prefixed)
|
||||||
return stripped
|
return stripped
|
||||||
},
|
},
|
||||||
'JSON File': (input, password) => {
|
'JSON File': (input, password) => {
|
||||||
|
@ -16,6 +16,7 @@ const ExtensionPlatform = require('./platforms/extension')
|
|||||||
const Migrator = require('./lib/migrator/')
|
const Migrator = require('./lib/migrator/')
|
||||||
const migrations = require('./migrations/')
|
const migrations = require('./migrations/')
|
||||||
const PortStream = require('./lib/port-stream.js')
|
const PortStream = require('./lib/port-stream.js')
|
||||||
|
const createStreamSink = require('./lib/createStreamSink')
|
||||||
const NotificationManager = require('./lib/notification-manager.js')
|
const NotificationManager = require('./lib/notification-manager.js')
|
||||||
const MetamaskController = require('./metamask-controller')
|
const MetamaskController = require('./metamask-controller')
|
||||||
const firstTimeState = require('./first-time-state')
|
const firstTimeState = require('./first-time-state')
|
||||||
@ -273,7 +274,7 @@ function setupController (initState, initLangCode) {
|
|||||||
asStream(controller.store),
|
asStream(controller.store),
|
||||||
debounce(1000),
|
debounce(1000),
|
||||||
storeTransform(versionifyData),
|
storeTransform(versionifyData),
|
||||||
storeTransform(persistData),
|
createStreamSink(persistData),
|
||||||
(error) => {
|
(error) => {
|
||||||
log.error('MetaMask - Persistence pipeline failed', error)
|
log.error('MetaMask - Persistence pipeline failed', error)
|
||||||
}
|
}
|
||||||
@ -289,7 +290,7 @@ function setupController (initState, initLangCode) {
|
|||||||
return versionedData
|
return versionedData
|
||||||
}
|
}
|
||||||
|
|
||||||
function persistData (state) {
|
async function persistData (state) {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
throw new Error('MetaMask - updated state is missing', state)
|
throw new Error('MetaMask - updated state is missing', state)
|
||||||
}
|
}
|
||||||
@ -297,12 +298,13 @@ function setupController (initState, initLangCode) {
|
|||||||
throw new Error('MetaMask - updated state does not have data', state)
|
throw new Error('MetaMask - updated state does not have data', state)
|
||||||
}
|
}
|
||||||
if (localStore.isSupported) {
|
if (localStore.isSupported) {
|
||||||
localStore.set(state)
|
try {
|
||||||
.catch((err) => {
|
await localStore.set(state)
|
||||||
|
} catch (err) {
|
||||||
|
// log error so we dont break the pipeline
|
||||||
log.error('error setting state in local store:', err)
|
log.error('error setting state in local store:', err)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
return state
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -176,6 +176,7 @@ function blacklistedDomainCheck () {
|
|||||||
'webbyawards.com',
|
'webbyawards.com',
|
||||||
'cdn.shopify.com/s/javascripts/tricorder/xtld-read-only-frame.html',
|
'cdn.shopify.com/s/javascripts/tricorder/xtld-read-only-frame.html',
|
||||||
'adyen.com',
|
'adyen.com',
|
||||||
|
'gravityforms.com',
|
||||||
]
|
]
|
||||||
var currentUrl = window.location.href
|
var currentUrl = window.location.href
|
||||||
var currentRegex
|
var currentRegex
|
||||||
|
@ -89,14 +89,21 @@ module.exports = class NetworkController extends EventEmitter {
|
|||||||
type: 'rpc',
|
type: 'rpc',
|
||||||
rpcTarget,
|
rpcTarget,
|
||||||
}
|
}
|
||||||
this.providerStore.updateState(providerConfig)
|
this.providerConfig = providerConfig
|
||||||
this._switchNetwork(providerConfig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async setProviderType (type) {
|
async setProviderType (type) {
|
||||||
assert.notEqual(type, 'rpc', `NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`)
|
assert.notEqual(type, 'rpc', `NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`)
|
||||||
assert(INFURA_PROVIDER_TYPES.includes(type) || type === LOCALHOST, `NetworkController - Unknown rpc type "${type}"`)
|
assert(INFURA_PROVIDER_TYPES.includes(type) || type === LOCALHOST, `NetworkController - Unknown rpc type "${type}"`)
|
||||||
const providerConfig = { type }
|
const providerConfig = { type }
|
||||||
|
this.providerConfig = providerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
resetConnection () {
|
||||||
|
this.providerConfig = this.getProviderConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
set providerConfig (providerConfig) {
|
||||||
this.providerStore.updateState(providerConfig)
|
this.providerStore.updateState(providerConfig)
|
||||||
this._switchNetwork(providerConfig)
|
this._switchNetwork(providerConfig)
|
||||||
}
|
}
|
||||||
|
@ -247,6 +247,7 @@ class PreferencesController {
|
|||||||
* @return {Promise<string>}
|
* @return {Promise<string>}
|
||||||
*/
|
*/
|
||||||
setAccountLabel (account, label) {
|
setAccountLabel (account, label) {
|
||||||
|
if (!account) throw new Error('setAccountLabel requires a valid address, got ' + String(account))
|
||||||
const address = normalizeAddress(account)
|
const address = normalizeAddress(account)
|
||||||
const {identities} = this.store.getState()
|
const {identities} = this.store.getState()
|
||||||
identities[address] = identities[address] || {}
|
identities[address] = identities[address] || {}
|
||||||
|
@ -10,6 +10,7 @@ const NonceTracker = require('./nonce-tracker')
|
|||||||
const txUtils = require('./lib/util')
|
const txUtils = require('./lib/util')
|
||||||
const cleanErrorStack = require('../../lib/cleanErrorStack')
|
const cleanErrorStack = require('../../lib/cleanErrorStack')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
|
const recipientBlacklistChecker = require('./lib/recipient-blacklist-checker')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Transaction Controller is an aggregate of sub-controllers and trackers
|
Transaction Controller is an aggregate of sub-controllers and trackers
|
||||||
@ -157,11 +158,14 @@ class TransactionController extends EventEmitter {
|
|||||||
let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams })
|
let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams })
|
||||||
this.addTx(txMeta)
|
this.addTx(txMeta)
|
||||||
this.emit('newUnapprovedTx', txMeta)
|
this.emit('newUnapprovedTx', txMeta)
|
||||||
// add default tx params
|
|
||||||
try {
|
try {
|
||||||
|
// check whether recipient account is blacklisted
|
||||||
|
recipientBlacklistChecker.checkAccount(txMeta.metamaskNetworkId, normalizedTxParams.to)
|
||||||
|
// add default tx params
|
||||||
txMeta = await this.addTxGasDefaults(txMeta)
|
txMeta = await this.addTxGasDefaults(txMeta)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
log.warn(error)
|
||||||
this.txStateManager.setTxStatusFailed(txMeta.id, error)
|
this.txStateManager.setTxStatusFailed(txMeta.id, error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@ -260,7 +264,12 @@ class TransactionController extends EventEmitter {
|
|||||||
// must set transaction to submitted/failed before releasing lock
|
// must set transaction to submitted/failed before releasing lock
|
||||||
nonceLock.releaseLock()
|
nonceLock.releaseLock()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.txStateManager.setTxStatusFailed(txId, err)
|
// this is try-catch wrapped so that we can guarantee that the nonceLock is released
|
||||||
|
try {
|
||||||
|
this.txStateManager.setTxStatusFailed(txId, err)
|
||||||
|
} catch (err) {
|
||||||
|
log.error(err)
|
||||||
|
}
|
||||||
// must set transaction to submitted/failed before releasing lock
|
// must set transaction to submitted/failed before releasing lock
|
||||||
if (nonceLock) nonceLock.releaseLock()
|
if (nonceLock) nonceLock.releaseLock()
|
||||||
// continue with error chain
|
// continue with error chain
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
const Config = require('./recipient-blacklist.js')
|
||||||
|
|
||||||
|
/** @module*/
|
||||||
|
module.exports = {
|
||||||
|
checkAccount,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a specified account on a specified network is blacklisted.
|
||||||
|
@param networkId {number}
|
||||||
|
@param account {string}
|
||||||
|
*/
|
||||||
|
function checkAccount (networkId, account) {
|
||||||
|
|
||||||
|
const mainnetId = 1
|
||||||
|
if (networkId !== mainnetId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountToCheck = account.toLowerCase()
|
||||||
|
if (Config.blacklist.includes(accountToCheck)) {
|
||||||
|
throw new Error('Recipient is a public account')
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
module.exports = {
|
||||||
|
'blacklist': [
|
||||||
|
// IDEX phisher
|
||||||
|
'0x9bcb0A9d99d815Bb87ee3191b1399b1Bcc46dc77',
|
||||||
|
// Ganache default seed phrases
|
||||||
|
'0x627306090abab3a6e1400e9345bc60c78a8bef57',
|
||||||
|
'0xf17f52151ebef6c7334fad080c5704d77216b732',
|
||||||
|
'0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef',
|
||||||
|
'0x821aea9a577a9b44299b9c15c88cf3087f3b5544',
|
||||||
|
'0x0d1d4e623d10f9fba5db95830f7d3839406c6af2',
|
||||||
|
'0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e',
|
||||||
|
'0x2191ef87e392377ec08e7c08eb105ef5448eced5',
|
||||||
|
'0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5',
|
||||||
|
'0x6330a553fc93768f612722bb8c2ec78ac90b3bbc',
|
||||||
|
'0x5aeda56215b167893e80b4fe645ba6d5bab767de',
|
||||||
|
],
|
||||||
|
}
|
@ -49,29 +49,35 @@ class NonceTracker {
|
|||||||
await this._globalMutexFree()
|
await this._globalMutexFree()
|
||||||
// await lock free, then take lock
|
// await lock free, then take lock
|
||||||
const releaseLock = await this._takeMutex(address)
|
const releaseLock = await this._takeMutex(address)
|
||||||
// evaluate multiple nextNonce strategies
|
try {
|
||||||
const nonceDetails = {}
|
// evaluate multiple nextNonce strategies
|
||||||
const networkNonceResult = await this._getNetworkNextNonce(address)
|
const nonceDetails = {}
|
||||||
const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address)
|
const networkNonceResult = await this._getNetworkNextNonce(address)
|
||||||
const nextNetworkNonce = networkNonceResult.nonce
|
const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address)
|
||||||
const highestSuggested = Math.max(nextNetworkNonce, highestLocallyConfirmed)
|
const nextNetworkNonce = networkNonceResult.nonce
|
||||||
|
const highestSuggested = Math.max(nextNetworkNonce, highestLocallyConfirmed)
|
||||||
|
|
||||||
const pendingTxs = this.getPendingTransactions(address)
|
const pendingTxs = this.getPendingTransactions(address)
|
||||||
const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0
|
const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0
|
||||||
|
|
||||||
nonceDetails.params = {
|
nonceDetails.params = {
|
||||||
highestLocallyConfirmed,
|
highestLocallyConfirmed,
|
||||||
highestSuggested,
|
highestSuggested,
|
||||||
nextNetworkNonce,
|
nextNetworkNonce,
|
||||||
|
}
|
||||||
|
nonceDetails.local = localNonceResult
|
||||||
|
nonceDetails.network = networkNonceResult
|
||||||
|
|
||||||
|
const nextNonce = Math.max(networkNonceResult.nonce, localNonceResult.nonce)
|
||||||
|
assert(Number.isInteger(nextNonce), `nonce-tracker - nextNonce is not an integer - got: (${typeof nextNonce}) "${nextNonce}"`)
|
||||||
|
|
||||||
|
// return nonce and release cb
|
||||||
|
return { nextNonce, nonceDetails, releaseLock }
|
||||||
|
} catch (err) {
|
||||||
|
// release lock if we encounter an error
|
||||||
|
releaseLock()
|
||||||
|
throw err
|
||||||
}
|
}
|
||||||
nonceDetails.local = localNonceResult
|
|
||||||
nonceDetails.network = networkNonceResult
|
|
||||||
|
|
||||||
const nextNonce = Math.max(networkNonceResult.nonce, localNonceResult.nonce)
|
|
||||||
assert(Number.isInteger(nextNonce), `nonce-tracker - nextNonce is not an integer - got: (${typeof nextNonce}) "${nextNonce}"`)
|
|
||||||
|
|
||||||
// return nonce and release cb
|
|
||||||
return { nextNonce, nonceDetails, releaseLock }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getCurrentBlock () {
|
async _getCurrentBlock () {
|
||||||
@ -85,8 +91,8 @@ class NonceTracker {
|
|||||||
|
|
||||||
async _globalMutexFree () {
|
async _globalMutexFree () {
|
||||||
const globalMutex = this._lookupMutex('global')
|
const globalMutex = this._lookupMutex('global')
|
||||||
const release = await globalMutex.acquire()
|
const releaseLock = await globalMutex.acquire()
|
||||||
release()
|
releaseLock()
|
||||||
}
|
}
|
||||||
|
|
||||||
async _takeMutex (lockId) {
|
async _takeMutex (lockId) {
|
||||||
|
@ -196,14 +196,14 @@ class PendingTransactionTracker extends EventEmitter {
|
|||||||
async _checkPendingTxs () {
|
async _checkPendingTxs () {
|
||||||
const signedTxList = this.getPendingTransactions()
|
const signedTxList = this.getPendingTransactions()
|
||||||
// in order to keep the nonceTracker accurate we block it while updating pending transactions
|
// in order to keep the nonceTracker accurate we block it while updating pending transactions
|
||||||
const nonceGlobalLock = await this.nonceTracker.getGlobalLock()
|
const { releaseLock } = await this.nonceTracker.getGlobalLock()
|
||||||
try {
|
try {
|
||||||
await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta)))
|
await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta)))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error('PendingTransactionWatcher - Error updating pending transactions')
|
log.error('PendingTransactionWatcher - Error updating pending transactions')
|
||||||
log.error(err)
|
log.error(err)
|
||||||
}
|
}
|
||||||
nonceGlobalLock.releaseLock()
|
releaseLock()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,9 +38,30 @@ web3.setProvider = function () {
|
|||||||
log.debug('MetaMask - overrode web3.setProvider')
|
log.debug('MetaMask - overrode web3.setProvider')
|
||||||
}
|
}
|
||||||
log.debug('MetaMask - injected web3')
|
log.debug('MetaMask - injected web3')
|
||||||
// export global web3, with usage-detection
|
|
||||||
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
|
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
|
||||||
|
|
||||||
|
// export global web3, with usage-detection and deprecation warning
|
||||||
|
|
||||||
|
/* TODO: Uncomment this area once auto-reload.js has been deprecated:
|
||||||
|
let hasBeenWarned = false
|
||||||
|
global.web3 = new Proxy(web3, {
|
||||||
|
get: (_web3, key) => {
|
||||||
|
// show warning once on web3 access
|
||||||
|
if (!hasBeenWarned && key !== 'currentProvider') {
|
||||||
|
console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider \nhttps://github.com/MetaMask/faq/blob/master/detecting_metamask.md#web3-deprecation')
|
||||||
|
hasBeenWarned = true
|
||||||
|
}
|
||||||
|
// return value normally
|
||||||
|
return _web3[key]
|
||||||
|
},
|
||||||
|
set: (_web3, key, value) => {
|
||||||
|
// set value normally
|
||||||
|
_web3[key] = value
|
||||||
|
},
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
// set web3 defaultAccount
|
// set web3 defaultAccount
|
||||||
inpageProvider.publicConfigStore.subscribe(function (state) {
|
inpageProvider.publicConfigStore.subscribe(function (state) {
|
||||||
web3.eth.defaultAccount = state.selectedAddress
|
web3.eth.defaultAccount = state.selectedAddress
|
||||||
|
24
app/scripts/lib/createStreamSink.js
Normal file
24
app/scripts/lib/createStreamSink.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
const WritableStream = require('readable-stream').Writable
|
||||||
|
const promiseToCallback = require('promise-to-callback')
|
||||||
|
|
||||||
|
module.exports = createStreamSink
|
||||||
|
|
||||||
|
|
||||||
|
function createStreamSink(asyncWriteFn, _opts) {
|
||||||
|
return new AsyncWritableStream(asyncWriteFn, _opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
class AsyncWritableStream extends WritableStream {
|
||||||
|
|
||||||
|
constructor (asyncWriteFn, _opts) {
|
||||||
|
const opts = Object.assign({ objectMode: true }, _opts)
|
||||||
|
super(opts)
|
||||||
|
this._asyncWriteFn = asyncWriteFn
|
||||||
|
}
|
||||||
|
|
||||||
|
// write from incomming stream to state
|
||||||
|
_write (chunk, encoding, callback) {
|
||||||
|
promiseToCallback(this._asyncWriteFn(chunk, encoding))(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,8 +2,7 @@ const extension = require('extensionizer')
|
|||||||
const promisify = require('pify')
|
const promisify = require('pify')
|
||||||
const allLocales = require('../../_locales/index.json')
|
const allLocales = require('../../_locales/index.json')
|
||||||
|
|
||||||
const isSupported = extension.i18n && extension.i18n.getAcceptLanguages
|
const getPreferredLocales = extension.i18n ? promisify(
|
||||||
const getPreferredLocales = isSupported ? promisify(
|
|
||||||
extension.i18n.getAcceptLanguages,
|
extension.i18n.getAcceptLanguages,
|
||||||
{ errorFirst: false }
|
{ errorFirst: false }
|
||||||
) : async () => []
|
) : async () => []
|
||||||
@ -18,7 +17,21 @@ const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().r
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async function getFirstPreferredLangCode () {
|
async function getFirstPreferredLangCode () {
|
||||||
const userPreferredLocaleCodes = await getPreferredLocales()
|
let userPreferredLocaleCodes
|
||||||
|
|
||||||
|
try {
|
||||||
|
userPreferredLocaleCodes = await getPreferredLocales()
|
||||||
|
} catch (e) {
|
||||||
|
// Brave currently throws when calling getAcceptLanguages, so this handles that.
|
||||||
|
userPreferredLocaleCodes = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeguard for Brave Browser until they implement chrome.i18n.getAcceptLanguages
|
||||||
|
// https://github.com/MetaMask/metamask-extension/issues/4270
|
||||||
|
if (!userPreferredLocaleCodes){
|
||||||
|
userPreferredLocaleCodes = []
|
||||||
|
}
|
||||||
|
|
||||||
const firstPreferredLangCode = userPreferredLocaleCodes
|
const firstPreferredLangCode = userPreferredLocaleCodes
|
||||||
.map(code => code.toLowerCase())
|
.map(code => code.toLowerCase())
|
||||||
.find(code => existingLocaleCodes.includes(code))
|
.find(code => existingLocaleCodes.includes(code))
|
||||||
@ -26,3 +39,4 @@ async function getFirstPreferredLangCode () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = getFirstPreferredLangCode
|
module.exports = getFirstPreferredLangCode
|
||||||
|
|
||||||
|
@ -46,7 +46,6 @@ const GWEI_BN = new BN('1000000000')
|
|||||||
const percentile = require('percentile')
|
const percentile = require('percentile')
|
||||||
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
|
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
|
||||||
const cleanErrorStack = require('./lib/cleanErrorStack')
|
const cleanErrorStack = require('./lib/cleanErrorStack')
|
||||||
const DiagnosticsReporter = require('./lib/diagnostics-reporter')
|
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
|
|
||||||
module.exports = class MetamaskController extends EventEmitter {
|
module.exports = class MetamaskController extends EventEmitter {
|
||||||
@ -65,12 +64,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
const initState = opts.initState || {}
|
const initState = opts.initState || {}
|
||||||
this.recordFirstTimeInfo(initState)
|
this.recordFirstTimeInfo(initState)
|
||||||
|
|
||||||
// metamask diagnostics reporter
|
|
||||||
this.diagnostics = opts.diagnostics || new DiagnosticsReporter({
|
|
||||||
firstTimeInfo: initState.firstTimeInfo,
|
|
||||||
version,
|
|
||||||
})
|
|
||||||
|
|
||||||
// platform-specific api
|
// platform-specific api
|
||||||
this.platform = opts.platform
|
this.platform = opts.platform
|
||||||
|
|
||||||
@ -92,7 +85,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
this.preferencesController = new PreferencesController({
|
this.preferencesController = new PreferencesController({
|
||||||
initState: initState.PreferencesController,
|
initState: initState.PreferencesController,
|
||||||
initLangCode: opts.initLangCode,
|
initLangCode: opts.initLangCode,
|
||||||
diagnostics: this.diagnostics,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// currency controller
|
// currency controller
|
||||||
@ -189,9 +181,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
version,
|
version,
|
||||||
firstVersion: initState.firstTimeInfo.version,
|
firstVersion: initState.firstTimeInfo.version,
|
||||||
})
|
})
|
||||||
this.noticeController.updateNoticesList()
|
|
||||||
// to be uncommented when retrieving notices from a remote server.
|
|
||||||
// this.noticeController.startPolling()
|
|
||||||
|
|
||||||
this.shapeshiftController = new ShapeShiftController({
|
this.shapeshiftController = new ShapeShiftController({
|
||||||
initState: initState.ShapeShiftController,
|
initState: initState.ShapeShiftController,
|
||||||
@ -394,6 +383,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
|
updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
|
||||||
retryTransaction: nodeify(this.retryTransaction, this),
|
retryTransaction: nodeify(this.retryTransaction, this),
|
||||||
getFilteredTxList: nodeify(txController.getFilteredTxList, txController),
|
getFilteredTxList: nodeify(txController.getFilteredTxList, txController),
|
||||||
|
isNonceTaken: nodeify(txController.isNonceTaken, txController),
|
||||||
|
estimateGas: nodeify(this.estimateGas, this),
|
||||||
|
|
||||||
// messageManager
|
// messageManager
|
||||||
signMessage: nodeify(this.signMessage, this),
|
signMessage: nodeify(this.signMessage, this),
|
||||||
@ -434,28 +425,24 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
* @returns {Object} vault
|
* @returns {Object} vault
|
||||||
*/
|
*/
|
||||||
async createNewVaultAndKeychain (password) {
|
async createNewVaultAndKeychain (password) {
|
||||||
const release = await this.createVaultMutex.acquire()
|
const releaseLock = await this.createVaultMutex.acquire()
|
||||||
let vault
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let vault
|
||||||
const accounts = await this.keyringController.getAccounts()
|
const accounts = await this.keyringController.getAccounts()
|
||||||
|
|
||||||
if (accounts.length > 0) {
|
if (accounts.length > 0) {
|
||||||
vault = await this.keyringController.fullUpdate()
|
vault = await this.keyringController.fullUpdate()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
vault = await this.keyringController.createNewVaultAndKeychain(password)
|
vault = await this.keyringController.createNewVaultAndKeychain(password)
|
||||||
const accounts = await this.keyringController.getAccounts()
|
const accounts = await this.keyringController.getAccounts()
|
||||||
this.preferencesController.setAddresses(accounts)
|
this.preferencesController.setAddresses(accounts)
|
||||||
this.selectFirstIdentity()
|
this.selectFirstIdentity()
|
||||||
}
|
}
|
||||||
release()
|
releaseLock()
|
||||||
|
return vault
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
release()
|
releaseLock()
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
|
|
||||||
return vault
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -464,7 +451,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
* @param {} seed
|
* @param {} seed
|
||||||
*/
|
*/
|
||||||
async createNewVaultAndRestore (password, seed) {
|
async createNewVaultAndRestore (password, seed) {
|
||||||
const release = await this.createVaultMutex.acquire()
|
const releaseLock = await this.createVaultMutex.acquire()
|
||||||
try {
|
try {
|
||||||
// clear known identities
|
// clear known identities
|
||||||
this.preferencesController.setAddresses([])
|
this.preferencesController.setAddresses([])
|
||||||
@ -474,10 +461,10 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
const accounts = await this.keyringController.getAccounts()
|
const accounts = await this.keyringController.getAccounts()
|
||||||
this.preferencesController.setAddresses(accounts)
|
this.preferencesController.setAddresses(accounts)
|
||||||
this.selectFirstIdentity()
|
this.selectFirstIdentity()
|
||||||
release()
|
releaseLock()
|
||||||
return vault
|
return vault
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
release()
|
releaseLock()
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -628,10 +615,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
async resetAccount () {
|
async resetAccount () {
|
||||||
const selectedAddress = this.preferencesController.getSelectedAddress()
|
const selectedAddress = this.preferencesController.getSelectedAddress()
|
||||||
this.txController.wipeTransactions(selectedAddress)
|
this.txController.wipeTransactions(selectedAddress)
|
||||||
|
this.networkController.resetConnection()
|
||||||
const networkController = this.networkController
|
|
||||||
const oldType = networkController.getProviderConfig().type
|
|
||||||
await networkController.setProviderType(oldType, true)
|
|
||||||
|
|
||||||
return selectedAddress
|
return selectedAddress
|
||||||
}
|
}
|
||||||
@ -958,6 +942,18 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
estimateGas (estimateGasParams) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
return this.txController.txGasUtil.query.estimateGas(estimateGasParams, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// PASSWORD MANAGEMENT
|
// PASSWORD MANAGEMENT
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
@ -2,7 +2,7 @@ const EventEmitter = require('events').EventEmitter
|
|||||||
const semver = require('semver')
|
const semver = require('semver')
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const hardCodedNotices = require('../../notices/notices.json')
|
const hardCodedNotices = require('../../notices/notices.js')
|
||||||
const uniqBy = require('lodash.uniqby')
|
const uniqBy = require('lodash.uniqby')
|
||||||
|
|
||||||
module.exports = class NoticeController extends EventEmitter {
|
module.exports = class NoticeController extends EventEmitter {
|
||||||
@ -16,8 +16,12 @@ module.exports = class NoticeController extends EventEmitter {
|
|||||||
noticesList: [],
|
noticesList: [],
|
||||||
}, opts.initState)
|
}, opts.initState)
|
||||||
this.store = new ObservableStore(initState)
|
this.store = new ObservableStore(initState)
|
||||||
|
// setup memStore
|
||||||
this.memStore = new ObservableStore({})
|
this.memStore = new ObservableStore({})
|
||||||
this.store.subscribe(() => this._updateMemstore())
|
this.store.subscribe(() => this._updateMemstore())
|
||||||
|
this._updateMemstore()
|
||||||
|
// pull in latest notices
|
||||||
|
this.updateNoticesList()
|
||||||
}
|
}
|
||||||
|
|
||||||
getNoticesList () {
|
getNoticesList () {
|
||||||
@ -29,9 +33,9 @@ module.exports = class NoticeController extends EventEmitter {
|
|||||||
return notices.filter((notice) => notice.read === false)
|
return notices.filter((notice) => notice.read === false)
|
||||||
}
|
}
|
||||||
|
|
||||||
getLatestUnreadNotice () {
|
getNextUnreadNotice () {
|
||||||
const unreadNotices = this.getUnreadNotices()
|
const unreadNotices = this.getUnreadNotices()
|
||||||
return unreadNotices[unreadNotices.length - 1]
|
return unreadNotices[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
async setNoticesList (noticesList) {
|
async setNoticesList (noticesList) {
|
||||||
@ -47,7 +51,7 @@ module.exports = class NoticeController extends EventEmitter {
|
|||||||
notices[index].read = true
|
notices[index].read = true
|
||||||
notices[index].body = ''
|
notices[index].body = ''
|
||||||
this.setNoticesList(notices)
|
this.setNoticesList(notices)
|
||||||
const latestNotice = this.getLatestUnreadNotice()
|
const latestNotice = this.getNextUnreadNotice()
|
||||||
cb(null, latestNotice)
|
cb(null, latestNotice)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
cb(err)
|
cb(err)
|
||||||
@ -64,15 +68,6 @@ module.exports = class NoticeController extends EventEmitter {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
startPolling () {
|
|
||||||
if (this.noticePoller) {
|
|
||||||
clearInterval(this.noticePoller)
|
|
||||||
}
|
|
||||||
this.noticePoller = setInterval(() => {
|
|
||||||
this.noticeController.updateNoticesList()
|
|
||||||
}, 300000)
|
|
||||||
}
|
|
||||||
|
|
||||||
_mergeNotices (oldNotices, newNotices) {
|
_mergeNotices (oldNotices, newNotices) {
|
||||||
return uniqBy(oldNotices.concat(newNotices), 'id')
|
return uniqBy(oldNotices.concat(newNotices), 'id')
|
||||||
}
|
}
|
||||||
@ -91,19 +86,15 @@ module.exports = class NoticeController extends EventEmitter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_mapNoticeIds (notices) {
|
|
||||||
return notices.map((notice) => notice.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
async _retrieveNoticeData () {
|
async _retrieveNoticeData () {
|
||||||
// Placeholder for the API.
|
// Placeholder for remote notice API.
|
||||||
return hardCodedNotices
|
return hardCodedNotices
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateMemstore () {
|
_updateMemstore () {
|
||||||
const lastUnreadNotice = this.getLatestUnreadNotice()
|
const nextUnreadNotice = this.getNextUnreadNotice()
|
||||||
const noActiveNotices = !lastUnreadNotice
|
const noActiveNotices = !nextUnreadNotice
|
||||||
this.memStore.updateState({ lastUnreadNotice, noActiveNotices })
|
this.memStore.updateState({ nextUnreadNotice, noActiveNotices })
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -75,9 +75,9 @@
|
|||||||
{
|
{
|
||||||
"type": "HD Key Tree",
|
"type": "HD Key Tree",
|
||||||
"accounts": [
|
"accounts": [
|
||||||
"fdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||||
"c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
|
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
|
||||||
"2f8d4a878cfa04a6e60d46362f5644deab66572d"
|
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -52,7 +52,7 @@
|
|||||||
"conversionRate": 12.7200827,
|
"conversionRate": 12.7200827,
|
||||||
"conversionDate": 1487363041,
|
"conversionDate": 1487363041,
|
||||||
"noActiveNotices": true,
|
"noActiveNotices": true,
|
||||||
"lastUnreadNotice": {
|
"nextUnreadNotice": {
|
||||||
"read": true,
|
"read": true,
|
||||||
"date": "Thu Feb 09 2017",
|
"date": "Thu Feb 09 2017",
|
||||||
"title": "Terms of Use",
|
"title": "Terms of Use",
|
||||||
|
@ -151,5 +151,10 @@
|
|||||||
"scrollToBottom": false,
|
"scrollToBottom": false,
|
||||||
"forgottenPassword": null
|
"forgottenPassword": null
|
||||||
},
|
},
|
||||||
"identities": {}
|
"identities": {},
|
||||||
|
"send": {
|
||||||
|
"fromDropdownOpen": false,
|
||||||
|
"toDropdownOpen": false,
|
||||||
|
"errors": {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,9 +115,9 @@
|
|||||||
{
|
{
|
||||||
"type": "HD Key Tree",
|
"type": "HD Key Tree",
|
||||||
"accounts": [
|
"accounts": [
|
||||||
"fdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||||
"c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
|
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
|
||||||
"2f8d4a878cfa04a6e60d46362f5644deab66572d"
|
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -76,9 +76,9 @@
|
|||||||
{
|
{
|
||||||
"type": "HD Key Tree",
|
"type": "HD Key Tree",
|
||||||
"accounts": [
|
"accounts": [
|
||||||
"fdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||||
"c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
|
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
|
||||||
"2f8d4a878cfa04a6e60d46362f5644deab66572d"
|
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"conversionRate": 12.7527416,
|
"conversionRate": 12.7527416,
|
||||||
"conversionDate": 1487624341,
|
"conversionDate": 1487624341,
|
||||||
"noActiveNotices": false,
|
"noActiveNotices": false,
|
||||||
"lastUnreadNotice": {
|
"nextUnreadNotice": {
|
||||||
"read": false,
|
"read": false,
|
||||||
"date": "Thu Feb 09 2017",
|
"date": "Thu Feb 09 2017",
|
||||||
"title": "Terms of Use",
|
"title": "Terms of Use",
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"conversionRate": 8.3533002,
|
"conversionRate": 8.3533002,
|
||||||
"conversionDate": 1481671082,
|
"conversionDate": 1481671082,
|
||||||
"noActiveNotices": false,
|
"noActiveNotices": false,
|
||||||
"lastUnreadNotice": {
|
"nextUnreadNotice": {
|
||||||
"read": false,
|
"read": false,
|
||||||
"date": "Tue Dec 13 2016",
|
"date": "Tue Dec 13 2016",
|
||||||
"title": "MultiVault Support",
|
"title": "MultiVault Support",
|
||||||
|
@ -94,9 +94,9 @@
|
|||||||
{
|
{
|
||||||
"type": "HD Key Tree",
|
"type": "HD Key Tree",
|
||||||
"accounts": [
|
"accounts": [
|
||||||
"fdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||||
"c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
|
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
|
||||||
"2f8d4a878cfa04a6e60d46362f5644deab66572d"
|
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -151,5 +151,10 @@
|
|||||||
"scrollToBottom": false,
|
"scrollToBottom": false,
|
||||||
"forgottenPassword": null
|
"forgottenPassword": null
|
||||||
},
|
},
|
||||||
"identities": {}
|
"identities": {},
|
||||||
|
"send": {
|
||||||
|
"fromDropdownOpen": false,
|
||||||
|
"toDropdownOpen": false,
|
||||||
|
"errors": {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,9 +76,9 @@
|
|||||||
{
|
{
|
||||||
"type": "HD Key Tree",
|
"type": "HD Key Tree",
|
||||||
"accounts": [
|
"accounts": [
|
||||||
"fdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||||
"c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
|
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
|
||||||
"2f8d4a878cfa04a6e60d46362f5644deab66572d"
|
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -130,5 +130,10 @@
|
|||||||
"scrollToBottom": false,
|
"scrollToBottom": false,
|
||||||
"forgottenPassword": null
|
"forgottenPassword": null
|
||||||
},
|
},
|
||||||
"identities": {}
|
"identities": {},
|
||||||
|
"send": {
|
||||||
|
"fromDropdownOpen": false,
|
||||||
|
"toDropdownOpen": false,
|
||||||
|
"errors": {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,9 +83,9 @@
|
|||||||
{
|
{
|
||||||
"type": "HD Key Tree",
|
"type": "HD Key Tree",
|
||||||
"accounts": [
|
"accounts": [
|
||||||
"fdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||||
"c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
|
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
|
||||||
"2f8d4a878cfa04a6e60d46362f5644deab66572d"
|
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -124,5 +124,10 @@
|
|||||||
"scrollToBottom": false,
|
"scrollToBottom": false,
|
||||||
"forgottenPassword": null
|
"forgottenPassword": null
|
||||||
},
|
},
|
||||||
"identities": {}
|
"identities": {},
|
||||||
|
"send": {
|
||||||
|
"fromDropdownOpen": false,
|
||||||
|
"toDropdownOpen": false,
|
||||||
|
"errors": {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
234
gentests.js
Normal file
234
gentests.js
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const async = require('async')
|
||||||
|
const path = require('path')
|
||||||
|
const promisify = require('pify')
|
||||||
|
|
||||||
|
// start(/\.selectors.js/, generateSelectorTest).catch(console.error)
|
||||||
|
// start(/\.utils.js/, generateUtilTest).catch(console.error)
|
||||||
|
startContainer(/\.container.js/, generateContainerTest).catch(console.error)
|
||||||
|
|
||||||
|
async function getAllFileNames (dirName) {
|
||||||
|
const rootPath = path.join(__dirname, dirName)
|
||||||
|
const allNames = (await promisify(fs.readdir)(dirName))
|
||||||
|
const fileNames = allNames.filter(name => name.match(/^.+\./))
|
||||||
|
const dirNames = allNames.filter(name => name.match(/^[^.]+$/))
|
||||||
|
|
||||||
|
const fullPathDirNames = dirNames.map(d => `${dirName}/${d}`)
|
||||||
|
const subNameArrays = await promisify(async.map)(fullPathDirNames, getAllFileNames)
|
||||||
|
let subNames = []
|
||||||
|
subNameArrays.forEach(subNameArray => subNames = [...subNames, ...subNameArray])
|
||||||
|
|
||||||
|
return [
|
||||||
|
...fileNames.map(name => dirName + '/' + name),
|
||||||
|
...subNames,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function start (fileRegEx, testGenerator) {
|
||||||
|
const fileNames = await getAllFileNames('./ui/app')
|
||||||
|
const sFiles = fileNames.filter(name => name.match(fileRegEx))
|
||||||
|
|
||||||
|
let sFileMethodNames
|
||||||
|
let testFilePath
|
||||||
|
async.each(sFiles, async (sFile, cb) => {
|
||||||
|
let [, sRootPath, sPath] = sFile.match(/^(.+\/)([^/]+)$/)
|
||||||
|
sFileMethodNames = Object.keys(require(__dirname + '/' + sFile))
|
||||||
|
|
||||||
|
testFilePath = sPath.replace('.', '-').replace('.', '.test.')
|
||||||
|
|
||||||
|
await promisify(fs.writeFile)(
|
||||||
|
`${__dirname}/${sRootPath}tests/${testFilePath}`,
|
||||||
|
testGenerator(sPath, sFileMethodNames),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
}, (err) => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startContainer (fileRegEx, testGenerator) {
|
||||||
|
const fileNames = await getAllFileNames('./ui/app')
|
||||||
|
const sFiles = fileNames.filter(name => name.match(fileRegEx))
|
||||||
|
|
||||||
|
let sFileMethodNames
|
||||||
|
async.each(sFiles, async (sFile, cb) => {
|
||||||
|
console.log(`sFile`, sFile);
|
||||||
|
let [, sRootPath, sPath] = sFile.match(/^(.+\/)([^/]+)$/)
|
||||||
|
|
||||||
|
let testFilePath = sPath.replace('.', '-').replace('.', '.test.')
|
||||||
|
|
||||||
|
await promisify(fs.readFile)(
|
||||||
|
__dirname + '/' + sFile,
|
||||||
|
'utf8',
|
||||||
|
async (err, result) => {
|
||||||
|
console.log(`result`, result.length);
|
||||||
|
const returnObjectStrings = result
|
||||||
|
.match(/return\s(\{[\s\S]+?})\n}/g)
|
||||||
|
.map(str => {
|
||||||
|
return str
|
||||||
|
.slice(0, str.length - 1)
|
||||||
|
.slice(7)
|
||||||
|
.replace(/\n/g, '')
|
||||||
|
.replace(/\s\s+/g, ' ')
|
||||||
|
|
||||||
|
})
|
||||||
|
const mapStateToPropsAssertionObject = returnObjectStrings[0]
|
||||||
|
.replace(/\w+:\s\w+\([\w,\s]+\),/g, str => {
|
||||||
|
const strKey = str.match(/^\w+/)[0]
|
||||||
|
return strKey + ': \'mock' + str.match(/^\w+/)[0].replace(/^./, c => c.toUpperCase()) + ':mockState\',\n'
|
||||||
|
})
|
||||||
|
.replace(/{\s\w.+/, firstLinePair => `{\n ${firstLinePair.slice(2)}`)
|
||||||
|
.replace(/\w+:.+,/g, s => ` ${s}`)
|
||||||
|
.replace(/}/g, s => ` ${s}`)
|
||||||
|
let mapDispatchToPropsMethodNames
|
||||||
|
if (returnObjectStrings[1]) {
|
||||||
|
mapDispatchToPropsMethodNames = returnObjectStrings[1].match(/\s\w+:\s/g).map(str => str.match(/\w+/)[0])
|
||||||
|
}
|
||||||
|
const proxyquireObject = ('{\n ' + result
|
||||||
|
.match(/import\s{[\s\S]+?}\sfrom\s.+/g)
|
||||||
|
.map(s => s.replace(/\n/g, ''))
|
||||||
|
.map((s, i) => {
|
||||||
|
const proxyKeys = s.match(/{.+}/)[0].match(/\w+/g)
|
||||||
|
return '\'' + s.match(/'(.+)'/)[1] + '\': { ' + (proxyKeys.length > 1
|
||||||
|
? '\n ' + proxyKeys.join(': () => {},\n ') + ': () => {},\n '
|
||||||
|
: proxyKeys[0] + ': () => {},') + ' }'
|
||||||
|
})
|
||||||
|
.join(',\n ') + '\n}')
|
||||||
|
.replace('{ connect: () => {}, },', `{
|
||||||
|
connect: (ms, md) => {
|
||||||
|
mapStateToProps = ms
|
||||||
|
mapDispatchToProps = md
|
||||||
|
return () => ({})
|
||||||
|
},
|
||||||
|
},`)
|
||||||
|
// console.log(`proxyquireObject`, proxyquireObject);
|
||||||
|
// console.log(`mapStateToPropsAssertionObject`, mapStateToPropsAssertionObject);
|
||||||
|
// console.log(`mapDispatchToPropsMethodNames`, mapDispatchToPropsMethodNames);
|
||||||
|
|
||||||
|
const containerTest = generateContainerTest(sPath, {
|
||||||
|
mapStateToPropsAssertionObject,
|
||||||
|
mapDispatchToPropsMethodNames,
|
||||||
|
proxyquireObject,
|
||||||
|
})
|
||||||
|
// console.log(`containerTest`, `${__dirname}/${sRootPath}tests/${testFilePath}`, containerTest);
|
||||||
|
console.log('----')
|
||||||
|
console.log(`sRootPath`, sRootPath);
|
||||||
|
console.log(`testFilePath`, testFilePath);
|
||||||
|
await promisify(fs.writeFile)(
|
||||||
|
`${__dirname}/${sRootPath}tests/${testFilePath}`,
|
||||||
|
containerTest,
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}, (err) => {
|
||||||
|
console.log('123', err)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateMethodList (methodArray) {
|
||||||
|
return methodArray.map(n => ' ' + n).join(',\n') + ','
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateMethodDescribeBlock (methodName, index) {
|
||||||
|
const describeBlock =
|
||||||
|
`${index ? ' ' : ''}describe('${methodName}()', () => {
|
||||||
|
it('should', () => {
|
||||||
|
const state = {}
|
||||||
|
|
||||||
|
assert.equal(${methodName}(state), )
|
||||||
|
})
|
||||||
|
})`
|
||||||
|
return describeBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateDispatchMethodDescribeBlock (methodName, index) {
|
||||||
|
const describeBlock =
|
||||||
|
`${index ? ' ' : ''}describe('${methodName}()', () => {
|
||||||
|
it('should dispatch an action', () => {
|
||||||
|
mapDispatchToPropsObject.${methodName}()
|
||||||
|
assert(dispatchSpy.calledOnce)
|
||||||
|
})
|
||||||
|
})`
|
||||||
|
return describeBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateMethodDescribeBlocks (methodArray) {
|
||||||
|
return methodArray
|
||||||
|
.map((methodName, index) => generateMethodDescribeBlock(methodName, index))
|
||||||
|
.join('\n\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateDispatchMethodDescribeBlocks (methodArray) {
|
||||||
|
return methodArray
|
||||||
|
.map((methodName, index) => generateDispatchMethodDescribeBlock(methodName, index))
|
||||||
|
.join('\n\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateSelectorTest (name, methodArray) {
|
||||||
|
return `import assert from 'assert'
|
||||||
|
import {
|
||||||
|
${generateMethodList(methodArray)}
|
||||||
|
} from '../${name}'
|
||||||
|
|
||||||
|
describe('${name.match(/^[^.]+/)} selectors', () => {
|
||||||
|
|
||||||
|
${generateMethodDescribeBlocks(methodArray)}
|
||||||
|
|
||||||
|
})`
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateUtilTest (name, methodArray) {
|
||||||
|
return `import assert from 'assert'
|
||||||
|
import {
|
||||||
|
${generateMethodList(methodArray)}
|
||||||
|
} from '../${name}'
|
||||||
|
|
||||||
|
describe('${name.match(/^[^.]+/)} utils', () => {
|
||||||
|
|
||||||
|
${generateMethodDescribeBlocks(methodArray)}
|
||||||
|
|
||||||
|
})`
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateContainerTest (sPath, {
|
||||||
|
mapStateToPropsAssertionObject,
|
||||||
|
mapDispatchToPropsMethodNames,
|
||||||
|
proxyquireObject,
|
||||||
|
}) {
|
||||||
|
return `import assert from 'assert'
|
||||||
|
import proxyquire from 'proxyquire'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
let mapStateToProps
|
||||||
|
let mapDispatchToProps
|
||||||
|
|
||||||
|
proxyquire('../${sPath}', ${proxyquireObject})
|
||||||
|
|
||||||
|
describe('${sPath.match(/^[^.]+/)} container', () => {
|
||||||
|
|
||||||
|
describe('mapStateToProps()', () => {
|
||||||
|
|
||||||
|
it('should map the correct properties to props', () => {
|
||||||
|
assert.deepEqual(mapStateToProps('mockState'), ${mapStateToPropsAssertionObject})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('mapDispatchToProps()', () => {
|
||||||
|
let dispatchSpy
|
||||||
|
let mapDispatchToPropsObject
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dispatchSpy = sinon.spy()
|
||||||
|
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
|
||||||
|
})
|
||||||
|
|
||||||
|
${mapDispatchToPropsMethodNames ? generateDispatchMethodDescribeBlocks(mapDispatchToPropsMethodNames) : 'delete'}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})`
|
||||||
|
}
|
@ -14,7 +14,7 @@ import LoadingScreen from './loading-screen'
|
|||||||
class NoticeScreen extends Component {
|
class NoticeScreen extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
address: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
lastUnreadNotice: PropTypes.shape({
|
nextUnreadNotice: PropTypes.shape({
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
date: PropTypes.string,
|
date: PropTypes.string,
|
||||||
body: PropTypes.string,
|
body: PropTypes.string,
|
||||||
@ -31,7 +31,7 @@ class NoticeScreen extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
lastUnreadNotice: {},
|
nextUnreadNotice: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -47,8 +47,8 @@ class NoticeScreen extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
acceptTerms = () => {
|
acceptTerms = () => {
|
||||||
const { markNoticeRead, lastUnreadNotice, history } = this.props
|
const { markNoticeRead, nextUnreadNotice, history } = this.props
|
||||||
markNoticeRead(lastUnreadNotice)
|
markNoticeRead(nextUnreadNotice)
|
||||||
.then(hasActiveNotices => {
|
.then(hasActiveNotices => {
|
||||||
if (!hasActiveNotices) {
|
if (!hasActiveNotices) {
|
||||||
history.push(INITIALIZE_BACKUP_PHRASE_ROUTE)
|
history.push(INITIALIZE_BACKUP_PHRASE_ROUTE)
|
||||||
@ -72,7 +72,7 @@ class NoticeScreen extends Component {
|
|||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
address,
|
address,
|
||||||
lastUnreadNotice: { title, body },
|
nextUnreadNotice: { title, body },
|
||||||
isLoading,
|
isLoading,
|
||||||
} = this.props
|
} = this.props
|
||||||
const { atBottom } = this.state
|
const { atBottom } = this.state
|
||||||
@ -113,12 +113,12 @@ class NoticeScreen extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = ({ metamask, appState }) => {
|
const mapStateToProps = ({ metamask, appState }) => {
|
||||||
const { selectedAddress, lastUnreadNotice, noActiveNotices } = metamask
|
const { selectedAddress, nextUnreadNotice, noActiveNotices } = metamask
|
||||||
const { isLoading } = appState
|
const { isLoading } = appState
|
||||||
|
|
||||||
return {
|
return {
|
||||||
address: selectedAddress,
|
address: selectedAddress,
|
||||||
lastUnreadNotice,
|
nextUnreadNotice,
|
||||||
noActiveNotices,
|
noActiveNotices,
|
||||||
isLoading,
|
isLoading,
|
||||||
}
|
}
|
||||||
|
6
notices/archive/notice_4.md
Normal file
6
notices/archive/notice_4.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Dear MetaMask Users,
|
||||||
|
|
||||||
|
There have been several instances of high-profile legitimate websites such as BTC Manager and Games Workshop that have had their websites temporarily compromised. This involves showing a fake MetaMask window on the page asking for user's seed phrases. MetaMask will never open itself in this way and users are encouraged to report these instances immediately to either [our phishing blacklist](https://github.com/MetaMask/eth-phishing-detect/issues) or our support email at [support@metamask.io](mailto:support@metamask.io).
|
||||||
|
|
||||||
|
Please read our full article on this ongoing issue at [https://medium.com/metamask/new-phishing-strategy-becoming-common-1b1123837168](https://medium.com/metamask/new-phishing-strategy-becoming-common-1b1123837168).
|
||||||
|
|
@ -1,27 +0,0 @@
|
|||||||
var fs = require('fs')
|
|
||||||
var path = require('path')
|
|
||||||
var prompt = require('prompt')
|
|
||||||
var open = require('open')
|
|
||||||
var extend = require('extend')
|
|
||||||
var notices = require('./notices.json')
|
|
||||||
|
|
||||||
|
|
||||||
console.log('List of Notices')
|
|
||||||
console.log(`ID \t DATE \t\t\t TITLE`)
|
|
||||||
notices.forEach((notice) => {
|
|
||||||
console.log(`${(' ' + notice.id).slice(-2)} \t ${notice.date} \t ${notice.title}`)
|
|
||||||
})
|
|
||||||
prompt.get(['id'], (error, res) => {
|
|
||||||
prompt.start()
|
|
||||||
if (error) {
|
|
||||||
console.log("Exiting...")
|
|
||||||
process.exit()
|
|
||||||
}
|
|
||||||
var index = notices.findIndex((notice) => { return notice.id == res.id})
|
|
||||||
if (index === -1) {
|
|
||||||
console.log('Notice not found. Exiting...')
|
|
||||||
}
|
|
||||||
notices.splice(index, 1)
|
|
||||||
fs.unlink(`notices/archive/notice_${res.id}.md`)
|
|
||||||
fs.writeFile(`notices/notices.json`, JSON.stringify(notices))
|
|
||||||
})
|
|
@ -1,33 +0,0 @@
|
|||||||
var fsp = require('fs-promise')
|
|
||||||
var path = require('path')
|
|
||||||
var prompt = require('prompt')
|
|
||||||
var open = require('open')
|
|
||||||
var extend = require('extend')
|
|
||||||
var notices = require('./notices.json')
|
|
||||||
var id = Number(require('./notice-nonce.json'))
|
|
||||||
|
|
||||||
var date = new Date().toDateString()
|
|
||||||
|
|
||||||
var notice = {
|
|
||||||
read: false,
|
|
||||||
date: date,
|
|
||||||
}
|
|
||||||
|
|
||||||
fsp.writeFile(`notices/archive/notice_${id}.md`,'Message goes here. Please write out your notice and save before proceeding at the command line.')
|
|
||||||
.then(() => {
|
|
||||||
open(`notices/archive/notice_${id}.md`)
|
|
||||||
prompt.start()
|
|
||||||
prompt.get(['title'], (err, result) => {
|
|
||||||
notice.title = result.title
|
|
||||||
fsp.readFile(`notices/archive/notice_${id}.md`)
|
|
||||||
.then((body) => {
|
|
||||||
notice.body = body.toString()
|
|
||||||
notice.id = id
|
|
||||||
notices.push(notice)
|
|
||||||
return fsp.writeFile(`notices/notices.json`, JSON.stringify(notices))
|
|
||||||
}).then((completion) => {
|
|
||||||
id += 1
|
|
||||||
return fsp.writeFile(`notices/notice-nonce.json`, id)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
@ -1 +0,0 @@
|
|||||||
4
|
|
34
notices/notices.js
Normal file
34
notices/notices.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// fs.readFileSync is inlined by browserify transform "brfs"
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
read: false,
|
||||||
|
date: 'Thu Feb 09 2017',
|
||||||
|
title: 'Terms of Use',
|
||||||
|
body: fs.readFileSync(__dirname + '/archive/notice_0.md', 'utf8'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
read: false,
|
||||||
|
date: 'Mon May 08 2017',
|
||||||
|
title: 'Privacy Notice',
|
||||||
|
body: fs.readFileSync(__dirname + '/archive/notice_2.md', 'utf8'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
read: false,
|
||||||
|
date: 'Tue Nov 28 2017',
|
||||||
|
title: 'Seed Phrase Alert',
|
||||||
|
firstVersion: '<=3.12.0',
|
||||||
|
body: fs.readFileSync(__dirname + '/archive/notice_3.md', 'utf8'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
read: false,
|
||||||
|
date: 'Wed Jun 13 2018',
|
||||||
|
title: 'Phishing Warning',
|
||||||
|
body: fs.readFileSync(__dirname + '/archive/notice_4.md', 'utf8'),
|
||||||
|
}
|
||||||
|
]
|
File diff suppressed because one or more lines are too long
@ -73,7 +73,7 @@ function mapStateToProps (state) {
|
|||||||
network: state.metamask.network,
|
network: state.metamask.network,
|
||||||
provider: state.metamask.provider,
|
provider: state.metamask.provider,
|
||||||
forgottenPassword: state.appState.forgottenPassword,
|
forgottenPassword: state.appState.forgottenPassword,
|
||||||
lastUnreadNotice: state.metamask.lastUnreadNotice,
|
nextUnreadNotice: state.metamask.nextUnreadNotice,
|
||||||
lostAccounts: state.metamask.lostAccounts,
|
lostAccounts: state.metamask.lostAccounts,
|
||||||
frequentRpcList: state.metamask.frequentRpcList || [],
|
frequentRpcList: state.metamask.frequentRpcList || [],
|
||||||
featureFlags,
|
featureFlags,
|
||||||
@ -460,9 +460,9 @@ App.prototype.renderPrimary = function () {
|
|||||||
}, [
|
}, [
|
||||||
|
|
||||||
h(NoticeScreen, {
|
h(NoticeScreen, {
|
||||||
notice: props.lastUnreadNotice,
|
notice: props.nextUnreadNotice,
|
||||||
key: 'NoticeScreen',
|
key: 'NoticeScreen',
|
||||||
onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
|
onConfirm: () => props.dispatch(actions.markNoticeRead(props.nextUnreadNotice)),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
!props.isInitialized && h('.flex-row.flex-center.flex-grow', [
|
!props.isInitialized && h('.flex-row.flex-center.flex-grow', [
|
||||||
|
@ -23,9 +23,10 @@ class AccountDropdowns extends Component {
|
|||||||
|
|
||||||
renderAccounts () {
|
renderAccounts () {
|
||||||
const { identities, selected, keyrings } = this.props
|
const { identities, selected, keyrings } = this.props
|
||||||
|
const accountOrder = keyrings.reduce((list, keyring) => list.concat(keyring.accounts), [])
|
||||||
|
|
||||||
return Object.keys(identities).map((key, index) => {
|
return accountOrder.map((address, index) => {
|
||||||
const identity = identities[key]
|
const identity = identities[address]
|
||||||
const isSelected = identity.address === selected
|
const isSelected = identity.address === selected
|
||||||
|
|
||||||
const simpleAddress = identity.address.substring(2).toLowerCase()
|
const simpleAddress = identity.address.substring(2).toLowerCase()
|
||||||
|
13018
package-lock.json
generated
13018
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,8 @@
|
|||||||
"dist": "gulp dist",
|
"dist": "gulp dist",
|
||||||
"doc": "jsdoc -c development/tools/.jsdoc.json",
|
"doc": "jsdoc -c development/tools/.jsdoc.json",
|
||||||
"test": "npm run test:unit && npm run test:integration && npm run lint",
|
"test": "npm run test:unit && npm run test:integration && npm run lint",
|
||||||
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" && dot-only-hunter",
|
"watch:test:unit": "nodemon --exec \"npm run test:unit\" ./test ./app ./ui",
|
||||||
|
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" \"ui/app/**/*.test.js\" && dot-only-hunter",
|
||||||
"test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js",
|
"test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js",
|
||||||
"test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara",
|
"test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara",
|
||||||
"test:integration:build": "gulp build:scss",
|
"test:integration:build": "gulp build:scss",
|
||||||
@ -45,8 +46,6 @@
|
|||||||
"disc": "gulp disc --debug",
|
"disc": "gulp disc --debug",
|
||||||
"announce": "node development/announcer.js",
|
"announce": "node development/announcer.js",
|
||||||
"version:bump": "node development/run-version-bump.js",
|
"version:bump": "node development/run-version-bump.js",
|
||||||
"generateNotice": "node notices/notice-generator.js",
|
|
||||||
"deleteNotice": "node notices/notice-delete.js",
|
|
||||||
"storybook": "start-storybook -p 6006 -c .storybook"
|
"storybook": "start-storybook -p 6006 -c .storybook"
|
||||||
},
|
},
|
||||||
"browserify": {
|
"browserify": {
|
||||||
@ -276,6 +275,7 @@
|
|||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"png-file-stream": "^1.0.0",
|
"png-file-stream": "^1.0.0",
|
||||||
"prompt": "^1.0.0",
|
"prompt": "^1.0.0",
|
||||||
|
"proxyquire": "2.0.1",
|
||||||
"qs": "^6.2.0",
|
"qs": "^6.2.0",
|
||||||
"qunitjs": "^2.4.1",
|
"qunitjs": "^2.4.1",
|
||||||
"radgrad-jsdoc-template": "^1.1.3",
|
"radgrad-jsdoc-template": "^1.1.3",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const webdriver = require('selenium-webdriver')
|
const webdriver = require('selenium-webdriver')
|
||||||
const { By, Key } = webdriver
|
const { By, Key, until } = webdriver
|
||||||
const {
|
const {
|
||||||
delay,
|
delay,
|
||||||
buildChromeWebDriver,
|
buildChromeWebDriver,
|
||||||
@ -14,8 +14,11 @@ const {
|
|||||||
checkBrowserForConsoleErrors,
|
checkBrowserForConsoleErrors,
|
||||||
loadExtension,
|
loadExtension,
|
||||||
verboseReportOnFailure,
|
verboseReportOnFailure,
|
||||||
|
findElement,
|
||||||
|
findElements,
|
||||||
} = require('./helpers')
|
} = require('./helpers')
|
||||||
|
|
||||||
|
|
||||||
describe('Using MetaMask with an existing account', function () {
|
describe('Using MetaMask with an existing account', function () {
|
||||||
let extensionId
|
let extensionId
|
||||||
let driver
|
let driver
|
||||||
@ -79,30 +82,33 @@ describe('Using MetaMask with an existing account', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('use the local network', async function () {
|
it('use the local network', async function () {
|
||||||
const [networkSelector] = await driver.findElements(By.css('#network_component'))
|
const networkSelector = await findElement(driver, By.css('#network_component'))
|
||||||
await networkSelector.click()
|
await networkSelector.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [localhost] = await driver.findElements(By.xpath(`//li[contains(text(), 'Localhost')]`))
|
const [localhost] = await findElements(driver, By.xpath(`//li[contains(text(), 'Localhost')]`))
|
||||||
await localhost.click()
|
await localhost.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('selects the new UI option', async () => {
|
it('selects the new UI option', async () => {
|
||||||
const button = await driver.findElement(By.xpath("//p[contains(text(), 'Try Beta Version')]"))
|
const button = await findElement(driver, By.xpath("//p[contains(text(), 'Try Beta Version')]"))
|
||||||
await button.click()
|
await button.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
// Close all other tabs
|
// Close all other tabs
|
||||||
const [oldUi, infoPage, newUi] = await driver.getAllWindowHandles()
|
let [oldUi, infoPage, newUi] = await driver.getAllWindowHandles()
|
||||||
|
newUi = newUi || infoPage
|
||||||
await driver.switchTo().window(oldUi)
|
await driver.switchTo().window(oldUi)
|
||||||
await driver.close()
|
await driver.close()
|
||||||
await driver.switchTo().window(infoPage)
|
if (infoPage !== newUi) {
|
||||||
await driver.close()
|
await driver.switchTo().window(infoPage)
|
||||||
|
await driver.close()
|
||||||
|
}
|
||||||
await driver.switchTo().window(newUi)
|
await driver.switchTo().window(newUi)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [continueBtn] = await driver.findElements(By.css('.welcome-screen__button'))
|
const continueBtn = await findElement(driver, By.css('.welcome-screen__button'))
|
||||||
await continueBtn.click()
|
await continueBtn.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
@ -110,48 +116,62 @@ describe('Using MetaMask with an existing account', function () {
|
|||||||
|
|
||||||
describe('First time flow starting from an existing seed phrase', () => {
|
describe('First time flow starting from an existing seed phrase', () => {
|
||||||
it('imports a seed phrase', async () => {
|
it('imports a seed phrase', async () => {
|
||||||
const [seedPhrase] = await driver.findElements(By.xpath(`//a[contains(text(), 'Import with seed phrase')]`))
|
const [seedPhrase] = await findElements(driver, By.xpath(`//a[contains(text(), 'Import with seed phrase')]`))
|
||||||
await seedPhrase.click()
|
await seedPhrase.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [seedTextArea] = await driver.findElements(By.css('textarea.import-account__secret-phrase'))
|
const [seedTextArea] = await findElements(driver, By.css('textarea.import-account__secret-phrase'))
|
||||||
await seedTextArea.sendKeys(testSeedPhrase)
|
await seedTextArea.sendKeys(testSeedPhrase)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [password] = await driver.findElements(By.id('password'))
|
const [password] = await findElements(driver, By.id('password'))
|
||||||
await password.sendKeys('correct horse battery staple')
|
await password.sendKeys('correct horse battery staple')
|
||||||
const [confirmPassword] = await driver.findElements(By.id('confirm-password'))
|
const [confirmPassword] = await findElements(driver, By.id('confirm-password'))
|
||||||
confirmPassword.sendKeys('correct horse battery staple')
|
confirmPassword.sendKeys('correct horse battery staple')
|
||||||
|
|
||||||
const [importButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Import')]`))
|
const [importButton] = await findElements(driver, By.xpath(`//button[contains(text(), 'Import')]`))
|
||||||
await importButton.click()
|
await importButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('clicks through the privacy notice', async () => {
|
it('clicks through the ToS', async () => {
|
||||||
const [nextScreen] = await driver.findElements(By.css('.tou button'))
|
// terms of use
|
||||||
await nextScreen.click()
|
|
||||||
await delay(regularDelayMs)
|
|
||||||
|
|
||||||
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
|
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
|
||||||
assert.equal(canClickThrough, false, 'disabled continue button')
|
assert.equal(canClickThrough, false, 'disabled continue button')
|
||||||
const element = await driver.findElement(By.linkText('Attributions'))
|
const bottomOfTos = await findElement(driver, By.linkText('Attributions'))
|
||||||
await driver.executeScript('arguments[0].scrollIntoView(true)', element)
|
await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
const acceptTos = await findElement(driver, By.css('.tou button'))
|
||||||
const [acceptTos] = await driver.findElements(By.css('.tou button'))
|
|
||||||
await acceptTos.click()
|
await acceptTos.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('clicks through the privacy notice', async () => {
|
||||||
|
// privacy notice
|
||||||
|
const nextScreen = await findElement(driver, By.css('.tou button'))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clicks through the phishing notice', async () => {
|
||||||
|
// phishing notice
|
||||||
|
const noticeElement = await driver.findElement(By.css('.markdown'))
|
||||||
|
await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', noticeElement)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
const nextScreen = await findElement(driver, By.css('.tou button'))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Show account information', () => {
|
describe('Show account information', () => {
|
||||||
it('shows the correct account address', async () => {
|
it('shows the correct account address', async () => {
|
||||||
await driver.findElement(By.css('.wallet-view__details-button')).click()
|
const detailsButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Details')]`))
|
||||||
|
detailsButton.click()
|
||||||
await driver.findElement(By.css('.qr-wrapper')).isDisplayed()
|
await driver.findElement(By.css('.qr-wrapper')).isDisplayed()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [address] = await driver.findElements(By.css('input.qr-ellip-address'))
|
const [address] = await findElements(driver, By.css('input.qr-ellip-address'))
|
||||||
assert.equal(await address.getAttribute('value'), testAddress)
|
assert.equal(await address.getAttribute('value'), testAddress)
|
||||||
|
|
||||||
await driver.executeScript("document.querySelector('.account-modal-close').click()")
|
await driver.executeScript("document.querySelector('.account-modal-close').click()")
|
||||||
@ -161,19 +181,22 @@ describe('Using MetaMask with an existing account', function () {
|
|||||||
it('shows a QR code for the account', async () => {
|
it('shows a QR code for the account', async () => {
|
||||||
await driver.findElement(By.css('.wallet-view__details-button')).click()
|
await driver.findElement(By.css('.wallet-view__details-button')).click()
|
||||||
await driver.findElement(By.css('.qr-wrapper')).isDisplayed()
|
await driver.findElement(By.css('.qr-wrapper')).isDisplayed()
|
||||||
|
const detailModal = await driver.findElement(By.css('span .modal'))
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
await driver.executeScript("document.querySelector('.account-modal-close').click()")
|
await driver.executeScript("document.querySelector('.account-modal-close').click()")
|
||||||
|
await driver.wait(until.stalenessOf(detailModal))
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Log out and log back in', () => {
|
describe('Log out and log back in', () => {
|
||||||
it('logs out of the account', async () => {
|
it('logs out of the account', async () => {
|
||||||
await driver.findElement(By.css('.account-menu__icon')).click()
|
const accountIdenticon = driver.findElement(By.css('.account-menu__icon .identicon'))
|
||||||
|
accountIdenticon.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [logoutButton] = await driver.findElements(By.css('.account-menu__logout-button'))
|
const [logoutButton] = await findElements(driver, By.css('.account-menu__logout-button'))
|
||||||
assert.equal(await logoutButton.getText(), 'Log out')
|
assert.equal(await logoutButton.getText(), 'Log out')
|
||||||
await logoutButton.click()
|
await logoutButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
@ -191,23 +214,23 @@ describe('Using MetaMask with an existing account', function () {
|
|||||||
await driver.findElement(By.css('.account-menu__icon')).click()
|
await driver.findElement(By.css('.account-menu__icon')).click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [createAccount] = await driver.findElements(By.xpath(`//div[contains(text(), 'Create Account')]`))
|
const [createAccount] = await findElements(driver, By.xpath(`//div[contains(text(), 'Create Account')]`))
|
||||||
await createAccount.click()
|
await createAccount.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('set account name', async () => {
|
it('set account name', async () => {
|
||||||
const [accountName] = await driver.findElements(By.css('.new-account-create-form input'))
|
const [accountName] = await findElements(driver, By.css('.new-account-create-form input'))
|
||||||
await accountName.sendKeys('2nd account')
|
await accountName.sendKeys('2nd account')
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [createButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create')]`))
|
const [createButton] = await findElements(driver, By.xpath(`//button[contains(text(), 'Create')]`))
|
||||||
await createButton.click()
|
await createButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show the correct account name', async () => {
|
it('should show the correct account name', async () => {
|
||||||
const [accountName] = await driver.findElements(By.css('.account-name'))
|
const [accountName] = await findElements(driver, By.css('.account-name'))
|
||||||
assert.equal(await accountName.getText(), '2nd account')
|
assert.equal(await accountName.getText(), '2nd account')
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
@ -218,7 +241,7 @@ describe('Using MetaMask with an existing account', function () {
|
|||||||
await driver.findElement(By.css('.account-menu__icon')).click()
|
await driver.findElement(By.css('.account-menu__icon')).click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [originalAccountMenuItem] = await driver.findElements(By.css('.account-menu__name'))
|
const [originalAccountMenuItem] = await findElements(driver, By.css('.account-menu__name'))
|
||||||
await originalAccountMenuItem.click()
|
await originalAccountMenuItem.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
@ -226,41 +249,41 @@ describe('Using MetaMask with an existing account', function () {
|
|||||||
|
|
||||||
describe('Send ETH from inside MetaMask', () => {
|
describe('Send ETH from inside MetaMask', () => {
|
||||||
it('starts to send a transaction', async function () {
|
it('starts to send a transaction', async function () {
|
||||||
const [sendButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Send')]`))
|
const sendButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`))
|
||||||
await sendButton.click()
|
await sendButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [inputAddress] = await driver.findElements(By.css('input[placeholder="Recipient Address"]'))
|
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
|
||||||
const [inputAmount] = await driver.findElements(By.css('.currency-display__input'))
|
const inputAmount = await findElement(driver, By.css('.currency-display__input'))
|
||||||
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
||||||
await inputAmount.sendKeys('1')
|
await inputAmount.sendKeys('1')
|
||||||
|
|
||||||
// Set the gas limit
|
// Set the gas limit
|
||||||
const [configureGas] = await driver.findElements(By.css('.send-v2__gas-fee-display button'))
|
const configureGas = await findElement(driver, By.css('.send-v2__gas-fee-display button'))
|
||||||
await configureGas.click()
|
await configureGas.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [save] = await driver.findElements(By.xpath(`//button[contains(text(), 'Save')]`))
|
const save = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`))
|
||||||
await save.click()
|
await save.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
// Continue to next screen
|
// Continue to next screen
|
||||||
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`))
|
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), 'Next')]`))
|
||||||
await nextScreen.click()
|
await nextScreen.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('confirms the transaction', async function () {
|
it('confirms the transaction', async function () {
|
||||||
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`))
|
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||||
await confirmButton.click()
|
await confirmButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('finds the transaction in the transactions list', async function () {
|
it('finds the transaction in the transactions list', async function () {
|
||||||
const transactions = await driver.findElements(By.css('.tx-list-item'))
|
const transactions = await findElements(driver, By.css('.tx-list-item'))
|
||||||
assert.equal(transactions.length, 1)
|
assert.equal(transactions.length, 1)
|
||||||
|
|
||||||
const txValues = await driver.findElements(By.css('.tx-list-value'))
|
const txValues = await findElements(driver, By.css('.tx-list-value'))
|
||||||
assert.equal(txValues.length, 1)
|
assert.equal(txValues.length, 1)
|
||||||
assert.equal(await txValues[0].getText(), '1 ETH')
|
assert.equal(await txValues[0].getText(), '1 ETH')
|
||||||
})
|
})
|
||||||
@ -275,7 +298,7 @@ describe('Using MetaMask with an existing account', function () {
|
|||||||
await driver.switchTo().window(faucet)
|
await driver.switchTo().window(faucet)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [send1eth] = await driver.findElements(By.xpath(`//button[contains(text(), '10 ether')]`))
|
const send1eth = await findElement(driver, By.xpath(`//button[contains(text(), '10 ether')]`), 14000)
|
||||||
await send1eth.click()
|
await send1eth.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
@ -283,7 +306,7 @@ describe('Using MetaMask with an existing account', function () {
|
|||||||
await loadExtension(driver, extensionId)
|
await loadExtension(driver, extensionId)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(),'Confirm')]`))
|
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`), 14000)
|
||||||
await confirmButton.click()
|
await confirmButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
@ -300,31 +323,31 @@ describe('Using MetaMask with an existing account', function () {
|
|||||||
|
|
||||||
describe('Add existing token using search', () => {
|
describe('Add existing token using search', () => {
|
||||||
it('clicks on the Add Token button', async () => {
|
it('clicks on the Add Token button', async () => {
|
||||||
const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`))
|
const addToken = await findElement(driver, By.xpath(`//button[contains(text(), 'Add Token')]`))
|
||||||
await addToken.click()
|
await addToken.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('picks an existing token', async () => {
|
it('picks an existing token', async () => {
|
||||||
const [tokenSearch] = await driver.findElements(By.css('input.add-token__input'))
|
const tokenSearch = await findElement(driver, By.css('#search-tokens'))
|
||||||
await tokenSearch.sendKeys('BAT')
|
await tokenSearch.sendKeys('BAT')
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [token] = await driver.findElements(By.xpath("//div[contains(text(), 'BAT')]"))
|
const token = await findElement(driver, By.xpath("//span[contains(text(), 'BAT')]"))
|
||||||
await token.click()
|
await token.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`))
|
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), 'Next')]`))
|
||||||
await nextScreen.click()
|
await nextScreen.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`))
|
const addTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Add Tokens')]`))
|
||||||
await addTokens.click()
|
await addTokens.click()
|
||||||
await delay(largeDelayMs)
|
await delay(largeDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders the balance for the new token', async () => {
|
it('renders the balance for the new token', async () => {
|
||||||
const balance = await driver.findElement(By.css('.tx-view .balance-display .token-amount'))
|
const balance = await findElement(driver, By.css('.tx-view .balance-display .token-amount'))
|
||||||
const tokenAmount = await balance.getText()
|
const tokenAmount = await balance.getText()
|
||||||
assert.equal(tokenAmount, '0BAT')
|
assert.equal(tokenAmount, '0BAT')
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
@ -343,14 +366,14 @@ describe('Using MetaMask with an existing account', function () {
|
|||||||
tokenName,
|
tokenName,
|
||||||
tokenDecimal,
|
tokenDecimal,
|
||||||
tokenSymbol,
|
tokenSymbol,
|
||||||
] = await driver.findElements(By.css('input'))
|
] = await findElements(driver, By.css('.form-control'))
|
||||||
|
|
||||||
await totalSupply.sendKeys('100')
|
await totalSupply.sendKeys('100')
|
||||||
await tokenName.sendKeys('Test')
|
await tokenName.sendKeys('Test')
|
||||||
await tokenDecimal.sendKeys('0')
|
await tokenDecimal.sendKeys('0')
|
||||||
await tokenSymbol.sendKeys('TST')
|
await tokenSymbol.sendKeys('TST')
|
||||||
|
|
||||||
const [createToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create Token')]`))
|
const createToken = await findElement(driver, By.xpath(`//button[contains(text(), 'Create Token')]`))
|
||||||
await createToken.click()
|
await createToken.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
@ -358,7 +381,7 @@ describe('Using MetaMask with an existing account', function () {
|
|||||||
await loadExtension(driver, extensionId)
|
await loadExtension(driver, extensionId)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(),'Confirm')]`))
|
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||||
await confirmButton.click()
|
await confirmButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
@ -373,31 +396,32 @@ describe('Using MetaMask with an existing account', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('clicks on the Add Token button', async () => {
|
it('clicks on the Add Token button', async () => {
|
||||||
const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`))
|
const addToken = await findElement(driver, By.xpath(`//button[contains(text(), 'Add Token')]`))
|
||||||
await addToken.click()
|
await addToken.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('picks the new Test token', async () => {
|
it('picks the new Test token', async () => {
|
||||||
const [addCustomToken] = await driver.findElements(By.xpath("//div[contains(text(), 'Custom Token')]"))
|
const addCustomToken = await findElement(driver, By.xpath("//div[contains(text(), 'Custom Token')]"))
|
||||||
await addCustomToken.click()
|
await addCustomToken.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [newTokenAddress] = await driver.findElements(By.css('.add-token__add-custom-form input'))
|
const newTokenAddress = await findElement(driver, By.css('#custom-address'))
|
||||||
await newTokenAddress.sendKeys(tokenAddress)
|
await newTokenAddress.sendKeys(tokenAddress)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`))
|
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), 'Next')]`))
|
||||||
await nextScreen.click()
|
await nextScreen.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`))
|
const addTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Add Tokens')]`))
|
||||||
await addTokens.click()
|
await addTokens.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders the balance for the new token', async () => {
|
it('renders the balance for the new token', async () => {
|
||||||
const [balance] = await driver.findElements(By.css('.tx-view .balance-display .token-amount'))
|
const balance = await findElement(driver, By.css('.tx-view .balance-display .token-amount'))
|
||||||
|
await driver.wait(until.elementTextIs(balance, '100TST'))
|
||||||
const tokenAmount = await balance.getText()
|
const tokenAmount = await balance.getText()
|
||||||
assert.equal(tokenAmount, '100TST')
|
assert.equal(tokenAmount, '100TST')
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const mkdirp = require('mkdirp')
|
const mkdirp = require('mkdirp')
|
||||||
const pify = require('pify')
|
const pify = require('pify')
|
||||||
|
const {until} = require('selenium-webdriver')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
checkBrowserForConsoleErrors,
|
checkBrowserForConsoleErrors,
|
||||||
loadExtension,
|
loadExtension,
|
||||||
verboseReportOnFailure,
|
verboseReportOnFailure,
|
||||||
|
findElement,
|
||||||
|
findElements,
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadExtension (driver, extensionId) {
|
async function loadExtension (driver, extensionId) {
|
||||||
@ -53,3 +56,11 @@ async function verboseReportOnFailure (driver, test) {
|
|||||||
const htmlSource = await driver.getPageSource()
|
const htmlSource = await driver.getPageSource()
|
||||||
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource)
|
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function findElement (driver, by, timeout = 10000) {
|
||||||
|
return driver.wait(until.elementLocated(by), timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findElements (driver, by, timeout = 10000) {
|
||||||
|
return driver.wait(until.elementsLocated(by), timeout)
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const webdriver = require('selenium-webdriver')
|
const webdriver = require('selenium-webdriver')
|
||||||
const { By, Key } = webdriver
|
const { By, Key, until } = webdriver
|
||||||
const {
|
const {
|
||||||
delay,
|
delay,
|
||||||
buildChromeWebDriver,
|
buildChromeWebDriver,
|
||||||
@ -11,6 +11,8 @@ const {
|
|||||||
getExtensionIdFirefox,
|
getExtensionIdFirefox,
|
||||||
} = require('../func')
|
} = require('../func')
|
||||||
const {
|
const {
|
||||||
|
findElement,
|
||||||
|
findElements,
|
||||||
checkBrowserForConsoleErrors,
|
checkBrowserForConsoleErrors,
|
||||||
loadExtension,
|
loadExtension,
|
||||||
verboseReportOnFailure,
|
verboseReportOnFailure,
|
||||||
@ -22,10 +24,10 @@ describe('MetaMask', function () {
|
|||||||
let tokenAddress
|
let tokenAddress
|
||||||
|
|
||||||
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'
|
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'
|
||||||
const tinyDelayMs = 500
|
const tinyDelayMs = 1000
|
||||||
const regularDelayMs = tinyDelayMs * 2
|
const regularDelayMs = tinyDelayMs * 2
|
||||||
const largeDelayMs = regularDelayMs * 2
|
const largeDelayMs = regularDelayMs * 2
|
||||||
const waitingNewPageDelayMs = regularDelayMs * 10
|
const waitingNewPageDelayMs = regularDelayMs * 30
|
||||||
|
|
||||||
this.timeout(0)
|
this.timeout(0)
|
||||||
this.bail(true)
|
this.bail(true)
|
||||||
@ -76,30 +78,33 @@ describe('MetaMask', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('use the local network', async function () {
|
it('use the local network', async function () {
|
||||||
const [networkSelector] = await driver.findElements(By.css('#network_component'))
|
const networkSelector = await findElement(driver, By.css('#network_component'))
|
||||||
await networkSelector.click()
|
await networkSelector.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [localhost] = await driver.findElements(By.xpath(`//li[contains(text(), 'Localhost')]`))
|
const localhost = await findElement(driver, By.xpath(`//li[contains(text(), 'Localhost')]`))
|
||||||
await localhost.click()
|
await localhost.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('selects the new UI option', async () => {
|
it('selects the new UI option', async () => {
|
||||||
const button = await driver.findElement(By.xpath("//p[contains(text(), 'Try Beta Version')]"))
|
const button = await findElement(driver, By.xpath("//p[contains(text(), 'Try Beta Version')]"))
|
||||||
await button.click()
|
await button.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
// Close all other tabs
|
// Close all other tabs
|
||||||
const [oldUi, infoPage, newUi] = await driver.getAllWindowHandles()
|
let [oldUi, infoPage, newUi] = await driver.getAllWindowHandles()
|
||||||
|
newUi = newUi || infoPage
|
||||||
await driver.switchTo().window(oldUi)
|
await driver.switchTo().window(oldUi)
|
||||||
await driver.close()
|
await driver.close()
|
||||||
await driver.switchTo().window(infoPage)
|
if (infoPage !== newUi) {
|
||||||
await driver.close()
|
await driver.switchTo().window(infoPage)
|
||||||
|
await driver.close()
|
||||||
|
}
|
||||||
await driver.switchTo().window(newUi)
|
await driver.switchTo().window(newUi)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [continueBtn] = await driver.findElements(By.css('.welcome-screen__button'))
|
const continueBtn = await findElement(driver, By.css('.welcome-screen__button'))
|
||||||
await continueBtn.click()
|
await continueBtn.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
@ -107,9 +112,9 @@ describe('MetaMask', function () {
|
|||||||
|
|
||||||
describe('Going through the first time flow', () => {
|
describe('Going through the first time flow', () => {
|
||||||
it('accepts a secure password', async () => {
|
it('accepts a secure password', async () => {
|
||||||
const [passwordBox] = await driver.findElements(By.css('.create-password #create-password'))
|
const passwordBox = await findElement(driver, By.css('.create-password #create-password'))
|
||||||
const [passwordBoxConfirm] = await driver.findElements(By.css('.create-password #confirm-password'))
|
const passwordBoxConfirm = await findElement(driver, By.css('.create-password #confirm-password'))
|
||||||
const [button] = await driver.findElements(By.css('.create-password button'))
|
const button = await findElement(driver, By.css('.create-password button'))
|
||||||
|
|
||||||
await passwordBox.sendKeys('correct horse battery staple')
|
await passwordBox.sendKeys('correct horse battery staple')
|
||||||
await passwordBoxConfirm.sendKeys('correct horse battery staple')
|
await passwordBoxConfirm.sendKeys('correct horse battery staple')
|
||||||
@ -118,31 +123,44 @@ describe('MetaMask', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('clicks through the unique image screen', async () => {
|
it('clicks through the unique image screen', async () => {
|
||||||
const [nextScreen] = await driver.findElements(By.css('.unique-image button'))
|
const nextScreen = await findElement(driver, By.css('.unique-image button'))
|
||||||
await nextScreen.click()
|
await nextScreen.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('clicks through the privacy notice', async () => {
|
it('clicks through the ToS', async () => {
|
||||||
const [nextScreen] = await driver.findElements(By.css('.tou button'))
|
// terms of use
|
||||||
await nextScreen.click()
|
|
||||||
await delay(regularDelayMs)
|
|
||||||
|
|
||||||
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
|
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
|
||||||
assert.equal(canClickThrough, false, 'disabled continue button')
|
assert.equal(canClickThrough, false, 'disabled continue button')
|
||||||
const [bottomOfTos] = await driver.findElements(By.linkText('Attributions'))
|
const bottomOfTos = await findElement(driver, By.linkText('Attributions'))
|
||||||
await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos)
|
await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
const acceptTos = await findElement(driver, By.css('.tou button'))
|
||||||
const [acceptTos] = await driver.findElements(By.css('.tou button'))
|
|
||||||
await acceptTos.click()
|
await acceptTos.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('clicks through the privacy notice', async () => {
|
||||||
|
// privacy notice
|
||||||
|
const nextScreen = await findElement(driver, By.css('.tou button'))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clicks through the phishing notice', async () => {
|
||||||
|
// phishing notice
|
||||||
|
const noticeElement = await driver.findElement(By.css('.markdown'))
|
||||||
|
await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', noticeElement)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
const nextScreen = await findElement(driver, By.css('.tou button'))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
let seedPhrase
|
let seedPhrase
|
||||||
|
|
||||||
it('reveals the seed phrase', async () => {
|
it('reveals the seed phrase', async () => {
|
||||||
const [revealSeedPhrase] = await driver.findElements(By.css('.backup-phrase__secret-blocker'))
|
const revealSeedPhrase = await findElement(driver, By.css('.backup-phrase__secret-blocker'))
|
||||||
await revealSeedPhrase.click()
|
await revealSeedPhrase.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
@ -150,7 +168,7 @@ describe('MetaMask', function () {
|
|||||||
assert.equal(seedPhrase.split(' ').length, 12)
|
assert.equal(seedPhrase.split(' ').length, 12)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [nextScreen] = await driver.findElements(By.css('.backup-phrase button'))
|
const nextScreen = await findElement(driver, By.css('.backup-phrase button'))
|
||||||
await nextScreen.click()
|
await nextScreen.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
@ -158,62 +176,64 @@ describe('MetaMask', function () {
|
|||||||
it('can retype the seed phrase', async () => {
|
it('can retype the seed phrase', async () => {
|
||||||
const words = seedPhrase.split(' ')
|
const words = seedPhrase.split(' ')
|
||||||
|
|
||||||
const [word0] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[0]}')]`))
|
const word0 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[0]}')]`))
|
||||||
await word0.click()
|
await word0.click()
|
||||||
await delay(tinyDelayMs)
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
const [word1] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[1]}')]`))
|
const word1 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[1]}')]`))
|
||||||
await word1.click()
|
await word1.click()
|
||||||
await delay(tinyDelayMs)
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
const [word2] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[2]}')]`))
|
const word2 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[2]}')]`))
|
||||||
await word2.click()
|
await word2.click()
|
||||||
await delay(tinyDelayMs)
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
const [word3] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[3]}')]`))
|
const word3 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[3]}')]`))
|
||||||
await word3.click()
|
await word3.click()
|
||||||
await delay(tinyDelayMs)
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
const [word4] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[4]}')]`))
|
const word4 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[4]}')]`))
|
||||||
await word4.click()
|
await word4.click()
|
||||||
await delay(tinyDelayMs)
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
const [word5] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[5]}')]`))
|
const word5 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[5]}')]`))
|
||||||
await word5.click()
|
await word5.click()
|
||||||
await delay(tinyDelayMs)
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
const [word6] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[6]}')]`))
|
const word6 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[6]}')]`))
|
||||||
await word6.click()
|
await word6.click()
|
||||||
await delay(tinyDelayMs)
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
const [word7] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[7]}')]`))
|
const word7 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[7]}')]`))
|
||||||
await word7.click()
|
await word7.click()
|
||||||
await delay(tinyDelayMs)
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
const [word8] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[8]}')]`))
|
const word8 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[8]}')]`))
|
||||||
await word8.click()
|
await word8.click()
|
||||||
await delay(tinyDelayMs)
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
const [word9] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[9]}')]`))
|
const word9 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[9]}')]`))
|
||||||
await word9.click()
|
await word9.click()
|
||||||
await delay(tinyDelayMs)
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
const [word10] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[10]}')]`))
|
const word10 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[10]}')]`))
|
||||||
await word10.click()
|
await word10.click()
|
||||||
await delay(tinyDelayMs)
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
const [word11] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[11]}')]`))
|
const word11 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[11]}')]`))
|
||||||
await word11.click()
|
await word11.click()
|
||||||
await delay(tinyDelayMs)
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
const [confirm] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`))
|
const confirm = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||||
await confirm.click()
|
await confirm.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('clicks through the deposit modal', async () => {
|
it('clicks through the deposit modal', async () => {
|
||||||
const [closeModal] = await driver.findElements(By.css('.page-container__header-close'))
|
const buyModal = await driver.findElement(By.css('span .modal'))
|
||||||
|
const closeModal = await findElement(driver, By.css('.page-container__header-close'))
|
||||||
await closeModal.click()
|
await closeModal.click()
|
||||||
|
await driver.wait(until.stalenessOf(buyModal))
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -234,7 +254,7 @@ describe('MetaMask', function () {
|
|||||||
await driver.findElement(By.css('.account-menu__icon')).click()
|
await driver.findElement(By.css('.account-menu__icon')).click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [logoutButton] = await driver.findElements(By.css('.account-menu__logout-button'))
|
const logoutButton = await findElement(driver, By.css('.account-menu__logout-button'))
|
||||||
assert.equal(await logoutButton.getText(), 'Log out')
|
assert.equal(await logoutButton.getText(), 'Log out')
|
||||||
await logoutButton.click()
|
await logoutButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
@ -252,23 +272,23 @@ describe('MetaMask', function () {
|
|||||||
await driver.findElement(By.css('.account-menu__icon')).click()
|
await driver.findElement(By.css('.account-menu__icon')).click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [createAccount] = await driver.findElements(By.xpath(`//div[contains(text(), 'Create Account')]`))
|
const createAccount = await findElement(driver, By.xpath(`//div[contains(text(), 'Create Account')]`))
|
||||||
await createAccount.click()
|
await createAccount.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('set account name', async () => {
|
it('set account name', async () => {
|
||||||
const [accountName] = await driver.findElements(By.css('.new-account-create-form input'))
|
const accountName = await findElement(driver, By.css('.new-account-create-form input'))
|
||||||
await accountName.sendKeys('2nd account')
|
await accountName.sendKeys('2nd account')
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [create] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create')]`))
|
const create = await findElement(driver, By.xpath(`//button[contains(text(), 'Create')]`))
|
||||||
await create.click()
|
await create.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should correct account name', async () => {
|
it('should correct account name', async () => {
|
||||||
const [accountName] = await driver.findElements(By.css('.account-name'))
|
const accountName = await findElement(driver, By.css('.account-name'))
|
||||||
assert.equal(await accountName.getText(), '2nd account')
|
assert.equal(await accountName.getText(), '2nd account')
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
@ -279,19 +299,19 @@ describe('MetaMask', function () {
|
|||||||
await driver.findElement(By.css('.account-menu__icon')).click()
|
await driver.findElement(By.css('.account-menu__icon')).click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [logoutButton] = await driver.findElements(By.css('.account-menu__logout-button'))
|
const logoutButton = await findElement(driver, By.css('.account-menu__logout-button'))
|
||||||
assert.equal(await logoutButton.getText(), 'Log out')
|
assert.equal(await logoutButton.getText(), 'Log out')
|
||||||
await logoutButton.click()
|
await logoutButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('imports seed phrase', async () => {
|
it('imports seed phrase', async () => {
|
||||||
const [restoreSeedLink] = await driver.findElements(By.css('.unlock-page__link--import'))
|
const restoreSeedLink = await findElement(driver, By.css('.unlock-page__link--import'))
|
||||||
assert.equal(await restoreSeedLink.getText(), 'Import using account seed phrase')
|
assert.equal(await restoreSeedLink.getText(), 'Import using account seed phrase')
|
||||||
await restoreSeedLink.click()
|
await restoreSeedLink.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [seedTextArea] = await driver.findElements(By.css('textarea'))
|
const seedTextArea = await findElement(driver, By.css('textarea'))
|
||||||
await seedTextArea.sendKeys(testSeedPhrase)
|
await seedTextArea.sendKeys(testSeedPhrase)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
@ -302,7 +322,7 @@ describe('MetaMask', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('balance renders', async () => {
|
it('balance renders', async () => {
|
||||||
const balance = await driver.findElement(By.css('.balance-display .token-amount'))
|
const balance = await findElement(driver, By.css('.balance-display .token-amount'))
|
||||||
const tokenAmount = await balance.getText()
|
const tokenAmount = await balance.getText()
|
||||||
assert.equal(tokenAmount, '100.000 ETH')
|
assert.equal(tokenAmount, '100.000 ETH')
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
@ -311,41 +331,41 @@ describe('MetaMask', function () {
|
|||||||
|
|
||||||
describe('Send ETH from inside MetaMask', () => {
|
describe('Send ETH from inside MetaMask', () => {
|
||||||
it('starts to send a transaction', async function () {
|
it('starts to send a transaction', async function () {
|
||||||
const [sendButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Send')]`))
|
const sendButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`))
|
||||||
await sendButton.click()
|
await sendButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [inputAddress] = await driver.findElements(By.css('input[placeholder="Recipient Address"]'))
|
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
|
||||||
const [inputAmount] = await driver.findElements(By.css('.currency-display__input'))
|
const inputAmount = await findElement(driver, By.css('.currency-display__input'))
|
||||||
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
||||||
await inputAmount.sendKeys('1')
|
await inputAmount.sendKeys('1')
|
||||||
|
|
||||||
// Set the gas limit
|
// Set the gas limit
|
||||||
const [configureGas] = await driver.findElements(By.css('.send-v2__gas-fee-display button'))
|
const configureGas = await findElement(driver, By.css('.send-v2__gas-fee-display button'))
|
||||||
await configureGas.click()
|
await configureGas.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [save] = await driver.findElements(By.xpath(`//button[contains(text(), 'Save')]`))
|
const save = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`))
|
||||||
await save.click()
|
await save.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
// Continue to next screen
|
// Continue to next screen
|
||||||
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`))
|
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), 'Next')]`))
|
||||||
await nextScreen.click()
|
await nextScreen.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('confirms the transaction', async function () {
|
it('confirms the transaction', async function () {
|
||||||
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`))
|
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||||
await confirmButton.click()
|
await confirmButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('finds the transaction in the transactions list', async function () {
|
it('finds the transaction in the transactions list', async function () {
|
||||||
const transactions = await driver.findElements(By.css('.tx-list-item'))
|
const transactions = await findElements(driver, By.css('.tx-list-item'))
|
||||||
assert.equal(transactions.length, 1)
|
assert.equal(transactions.length, 1)
|
||||||
|
|
||||||
const txValues = await driver.findElements(By.css('.tx-list-value'))
|
const txValues = await findElements(driver, By.css('.tx-list-value'))
|
||||||
assert.equal(txValues.length, 1)
|
assert.equal(txValues.length, 1)
|
||||||
assert.equal(await txValues[0].getText(), '1 ETH')
|
assert.equal(await txValues[0].getText(), '1 ETH')
|
||||||
})
|
})
|
||||||
@ -360,7 +380,7 @@ describe('MetaMask', function () {
|
|||||||
await driver.switchTo().window(faucet)
|
await driver.switchTo().window(faucet)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [send1eth] = await driver.findElements(By.xpath(`//button[contains(text(), '10 ether')]`))
|
const send1eth = await findElement(driver, By.xpath(`//button[contains(text(), '10 ether')]`), 14000)
|
||||||
await send1eth.click()
|
await send1eth.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
@ -368,7 +388,7 @@ describe('MetaMask', function () {
|
|||||||
await loadExtension(driver, extensionId)
|
await loadExtension(driver, extensionId)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`))
|
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`), 14000)
|
||||||
await confirmButton.click()
|
await confirmButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
@ -385,31 +405,32 @@ describe('MetaMask', function () {
|
|||||||
|
|
||||||
describe('Add existing token using search', () => {
|
describe('Add existing token using search', () => {
|
||||||
it('clicks on the Add Token button', async () => {
|
it('clicks on the Add Token button', async () => {
|
||||||
const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`))
|
const addToken = await findElement(driver, By.xpath(`//button[contains(text(), 'Add Token')]`))
|
||||||
await addToken.click()
|
await addToken.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can pick a token from the existing options', async () => {
|
it('can pick a token from the existing options', async () => {
|
||||||
const [tokenSearch] = await driver.findElements(By.css('input.add-token__input'))
|
const tokenSearch = await findElement(driver, By.css('#search-tokens'))
|
||||||
await tokenSearch.sendKeys('BAT')
|
await tokenSearch.sendKeys('BAT')
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [token] = await driver.findElements(By.xpath("//div[contains(text(), 'BAT')]"))
|
const token = await findElement(driver, By.xpath("//span[contains(text(), 'BAT')]"))
|
||||||
await token.click()
|
await token.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`))
|
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), 'Next')]`))
|
||||||
await nextScreen.click()
|
await nextScreen.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`))
|
const addTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Add Tokens')]`))
|
||||||
await addTokens.click()
|
await addTokens.click()
|
||||||
await delay(largeDelayMs)
|
await delay(largeDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders the balance for the chosen token', async () => {
|
it('renders the balance for the chosen token', async () => {
|
||||||
const balance = await driver.findElement(By.css('.tx-view .balance-display .token-amount'))
|
const balance = await findElement(driver, By.css('.tx-view .balance-display .token-amount'))
|
||||||
|
await driver.wait(until.elementTextIs(balance, '0BAT'))
|
||||||
const tokenAmount = await balance.getText()
|
const tokenAmount = await balance.getText()
|
||||||
assert.equal(tokenAmount, '0BAT')
|
assert.equal(tokenAmount, '0BAT')
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
@ -428,14 +449,14 @@ describe('MetaMask', function () {
|
|||||||
tokenName,
|
tokenName,
|
||||||
tokenDecimal,
|
tokenDecimal,
|
||||||
tokenSymbol,
|
tokenSymbol,
|
||||||
] = await driver.findElements(By.css('input'))
|
] = await findElements(driver, By.css('.form-control'))
|
||||||
|
|
||||||
await totalSupply.sendKeys('100')
|
await totalSupply.sendKeys('100')
|
||||||
await tokenName.sendKeys('Test')
|
await tokenName.sendKeys('Test')
|
||||||
await tokenDecimal.sendKeys('0')
|
await tokenDecimal.sendKeys('0')
|
||||||
await tokenSymbol.sendKeys('TST')
|
await tokenSymbol.sendKeys('TST')
|
||||||
|
|
||||||
const [createToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create Token')]`))
|
const createToken = await findElement(driver, By.xpath(`//button[contains(text(), 'Create Token')]`))
|
||||||
await createToken.click()
|
await createToken.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
@ -443,7 +464,7 @@ describe('MetaMask', function () {
|
|||||||
await loadExtension(driver, extensionId)
|
await loadExtension(driver, extensionId)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`))
|
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||||
await confirmButton.click()
|
await confirmButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
@ -458,31 +479,32 @@ describe('MetaMask', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('clicks on the Add Token button', async () => {
|
it('clicks on the Add Token button', async () => {
|
||||||
const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`))
|
const addToken = await findElement(driver, By.xpath(`//button[contains(text(), 'Add Token')]`))
|
||||||
await addToken.click()
|
await addToken.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('picks the newly created Test token', async () => {
|
it('picks the newly created Test token', async () => {
|
||||||
const [addCustomToken] = await driver.findElements(By.xpath("//div[contains(text(), 'Custom Token')]"))
|
const addCustomToken = await findElement(driver, By.xpath("//div[contains(text(), 'Custom Token')]"))
|
||||||
await addCustomToken.click()
|
await addCustomToken.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [newTokenAddress] = await driver.findElements(By.css('.add-token__add-custom-form input'))
|
const newTokenAddress = await findElement(driver, By.css('#custom-address'))
|
||||||
await newTokenAddress.sendKeys(tokenAddress)
|
await newTokenAddress.sendKeys(tokenAddress)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`))
|
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), 'Next')]`))
|
||||||
await nextScreen.click()
|
await nextScreen.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`))
|
const addTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Add Tokens')]`))
|
||||||
await addTokens.click()
|
await addTokens.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders the balance for the new token', async () => {
|
it('renders the balance for the new token', async () => {
|
||||||
const [balance] = await driver.findElements(By.css('.tx-view .balance-display .token-amount'))
|
const balance = await findElement(driver, By.css('.tx-view .balance-display .token-amount'))
|
||||||
|
await driver.wait(until.elementTextIs(balance, '100TST'))
|
||||||
const tokenAmount = await balance.getText()
|
const tokenAmount = await balance.getText()
|
||||||
assert.equal(tokenAmount, '100TST')
|
assert.equal(tokenAmount, '100TST')
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
@ -21,7 +21,7 @@ function delay (time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildChromeWebDriver (extPath) {
|
function buildChromeWebDriver (extPath) {
|
||||||
const tmpProfile = path.join(os.tmpdir(), fs.mkdtempSync('mm-chrome-profile'));
|
const tmpProfile = fs.mkdtempSync(path.join(os.tmpdir(), 'mm-chrome-profile'))
|
||||||
return new webdriver.Builder()
|
return new webdriver.Builder()
|
||||||
.withCapabilities({
|
.withCapabilities({
|
||||||
chromeOptions: {
|
chromeOptions: {
|
||||||
|
@ -71,13 +71,6 @@ describe('Metamask popup page', function () {
|
|||||||
it('matches MetaMask title', async () => {
|
it('matches MetaMask title', async () => {
|
||||||
const title = await driver.getTitle()
|
const title = await driver.getTitle()
|
||||||
assert.equal(title, 'MetaMask', 'title matches MetaMask')
|
assert.equal(title, 'MetaMask', 'title matches MetaMask')
|
||||||
})
|
|
||||||
|
|
||||||
it('shows privacy notice', async () => {
|
|
||||||
await delay(300)
|
|
||||||
const privacy = await driver.findElement(By.css('.terms-header')).getText()
|
|
||||||
assert.equal(privacy, 'PRIVACY NOTICE', 'shows privacy notice')
|
|
||||||
await driver.findElement(By.css('button')).click()
|
|
||||||
await delay(300)
|
await delay(300)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -100,6 +93,24 @@ describe('Metamask popup page', function () {
|
|||||||
await button.click()
|
await button.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('shows privacy notice', async () => {
|
||||||
|
const privacy = await driver.findElement(By.css('.terms-header')).getText()
|
||||||
|
assert.equal(privacy, 'PRIVACY NOTICE', 'shows privacy notice')
|
||||||
|
await driver.findElement(By.css('button')).click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows phishing notice', async () => {
|
||||||
|
await delay(300)
|
||||||
|
const noticeHeader = await driver.findElement(By.css('.terms-header')).getText()
|
||||||
|
assert.equal(noticeHeader, 'PHISHING WARNING', 'shows phishing warning')
|
||||||
|
const element = await driver.findElement(By.css('.markdown'))
|
||||||
|
await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', element)
|
||||||
|
await delay(300)
|
||||||
|
await driver.findElement(By.css('button')).click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
it('accepts password with length of eight', async () => {
|
it('accepts password with length of eight', async () => {
|
||||||
const passwordBox = await driver.findElement(By.id('password-box'))
|
const passwordBox = await driver.findElement(By.id('password-box'))
|
||||||
const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm'))
|
const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm'))
|
||||||
|
@ -101,7 +101,7 @@ async function runSendFlowTest(assert, done) {
|
|||||||
const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(2)')
|
const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(2)')
|
||||||
sendAmountField.find('.currency-display')[0].click()
|
sendAmountField.find('.currency-display')[0].click()
|
||||||
|
|
||||||
const sendAmountFieldInput = await findAsync(sendAmountField, 'input:text')
|
const sendAmountFieldInput = await findAsync(sendAmountField, '.currency-display__input')
|
||||||
sendAmountFieldInput.val('5.1')
|
sendAmountFieldInput.val('5.1')
|
||||||
reactTriggerChange(sendAmountField.find('input')[0])
|
reactTriggerChange(sendAmountField.find('input')[0])
|
||||||
|
|
||||||
@ -117,17 +117,17 @@ async function runSendFlowTest(assert, done) {
|
|||||||
const sendGasField = await queryAsync($, '.send-v2__gas-fee-display')
|
const sendGasField = await queryAsync($, '.send-v2__gas-fee-display')
|
||||||
assert.equal(
|
assert.equal(
|
||||||
sendGasField.find('.currency-display__input-wrapper > input').val(),
|
sendGasField.find('.currency-display__input-wrapper > input').val(),
|
||||||
'0.000198',
|
'0.000021',
|
||||||
'send gas field should show estimated gas total'
|
'send gas field should show estimated gas total'
|
||||||
)
|
)
|
||||||
assert.equal(
|
assert.equal(
|
||||||
sendGasField.find('.currency-display__converted-value')[0].textContent,
|
sendGasField.find('.currency-display__converted-value')[0].textContent,
|
||||||
'$0.24 USD',
|
'$0.03 USD',
|
||||||
'send gas field should show estimated gas total converted to USD'
|
'send gas field should show estimated gas total converted to USD'
|
||||||
)
|
)
|
||||||
|
|
||||||
await customizeGas(assert, 0, 21000, '0', '$0.00 USD')
|
await customizeGas(assert, 0, 21000, '0', '$0.00 USD')
|
||||||
await customizeGas(assert, 500, 60000, '0.003', '$3.60 USD')
|
await customizeGas(assert, 500, 60000, '0.03', '$36.03 USD')
|
||||||
|
|
||||||
const sendButton = await queryAsync($, 'button.btn-primary.btn--large.page-container__footer-button')
|
const sendButton = await queryAsync($, 'button.btn-primary.btn--large.page-container__footer-button')
|
||||||
assert.equal(sendButton[0].textContent, 'Next', 'next button rendered')
|
assert.equal(sendButton[0].textContent, 'Next', 'next button rendered')
|
||||||
@ -165,7 +165,7 @@ async function runSendFlowTest(assert, done) {
|
|||||||
const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(2)')
|
const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(2)')
|
||||||
sendAmountFieldInEdit.find('.currency-display')[0].click()
|
sendAmountFieldInEdit.find('.currency-display')[0].click()
|
||||||
|
|
||||||
const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('input:text')
|
const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('.currency-display__input')
|
||||||
sendAmountFieldInputInEdit.val('1.0')
|
sendAmountFieldInputInEdit.val('1.0')
|
||||||
reactTriggerChange(sendAmountFieldInputInEdit[0])
|
reactTriggerChange(sendAmountFieldInputInEdit[0])
|
||||||
|
|
||||||
|
@ -1,31 +1,59 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const accountImporter = require('../../../app/scripts/account-import-strategies/index')
|
|
||||||
const ethUtil = require('ethereumjs-util')
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
const accountImporter = require('../../../app/scripts/account-import-strategies/index')
|
||||||
|
const { assertRejects } = require('../test-utils')
|
||||||
|
|
||||||
describe('Account Import Strategies', function () {
|
describe('Account Import Strategies', function () {
|
||||||
const privkey = '0x4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553'
|
const privkey = '0x4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553'
|
||||||
const json = '{"version":3,"id":"dbb54385-0a99-437f-83c0-647de9f244c3","address":"a7f92ce3fba24196cf6f4bd2e1eb3db282ba998c","Crypto":{"ciphertext":"bde13d9ade5c82df80281ca363320ce254a8a3a06535bbf6ffdeaf0726b1312c","cipherparams":{"iv":"fbf93718a57f26051b292f072f2e5b41"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"7ffe00488319dec48e4c49a120ca49c6afbde9272854c64d9541c83fc6acdffe","n":8192,"r":8,"p":1},"mac":"2adfd9c4bc1cdac4c85bddfb31d9e21a684e0e050247a70c5698facf6b7d4681"}}'
|
const json = '{"version":3,"id":"dbb54385-0a99-437f-83c0-647de9f244c3","address":"a7f92ce3fba24196cf6f4bd2e1eb3db282ba998c","Crypto":{"ciphertext":"bde13d9ade5c82df80281ca363320ce254a8a3a06535bbf6ffdeaf0726b1312c","cipherparams":{"iv":"fbf93718a57f26051b292f072f2e5b41"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"7ffe00488319dec48e4c49a120ca49c6afbde9272854c64d9541c83fc6acdffe","n":8192,"r":8,"p":1},"mac":"2adfd9c4bc1cdac4c85bddfb31d9e21a684e0e050247a70c5698facf6b7d4681"}}'
|
||||||
|
|
||||||
it('imports a private key and strips 0x prefix', async function () {
|
describe('private key import', function () {
|
||||||
const importPrivKey = await accountImporter.importAccount('Private Key', [ privkey ])
|
it('imports a private key and strips 0x prefix', async function () {
|
||||||
assert.equal(importPrivKey, ethUtil.stripHexPrefix(privkey))
|
const importPrivKey = await accountImporter.importAccount('Private Key', [ privkey ])
|
||||||
|
assert.equal(importPrivKey, ethUtil.stripHexPrefix(privkey))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error for empty string private key', async () => {
|
||||||
|
assertRejects(async function() {
|
||||||
|
await accountImporter.importAccount('Private Key', [ '' ])
|
||||||
|
}, Error, 'no empty strings')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error for undefined string private key', async () => {
|
||||||
|
assertRejects(async function () {
|
||||||
|
await accountImporter.importAccount('Private Key', [ undefined ])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error for undefined string private key', async () => {
|
||||||
|
assertRejects(async function () {
|
||||||
|
await accountImporter.importAccount('Private Key', [])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error for invalid private key', async () => {
|
||||||
|
assertRejects(async function () {
|
||||||
|
await accountImporter.importAccount('Private Key', [ 'popcorn' ])
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('fails when password is incorrect for keystore', async function () {
|
describe('JSON keystore import', function () {
|
||||||
const wrongPassword = 'password2'
|
it('fails when password is incorrect for keystore', async function () {
|
||||||
|
const wrongPassword = 'password2'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await accountImporter.importAccount('JSON File', [ json, wrongPassword])
|
await accountImporter.importAccount('JSON File', [ json, wrongPassword])
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
assert.equal(error.message, 'Key derivation failed - possibly wrong passphrase')
|
assert.equal(error.message, 'Key derivation failed - possibly wrong passphrase')
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('imports json string and password to return a private key', async function () {
|
||||||
|
const fileContentsPassword = 'password1'
|
||||||
|
const importJson = await accountImporter.importAccount('JSON File', [ json, fileContentsPassword])
|
||||||
|
assert.equal(importJson, '0x5733876abe94146069ce8bcbabbde2677f2e35fa33e875e92041ed2ac87e5bc7')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('imports json string and password to return a private key', async function () {
|
|
||||||
const fileContentsPassword = 'password1'
|
|
||||||
const importJson = await accountImporter.importAccount('JSON File', [ json, fileContentsPassword])
|
|
||||||
assert.equal(importJson, '0x5733876abe94146069ce8bcbabbde2677f2e35fa33e875e92041ed2ac87e5bc7')
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -53,6 +53,9 @@ describe('MetaMaskController', function () {
|
|||||||
},
|
},
|
||||||
initState: clone(firstTimeState),
|
initState: clone(firstTimeState),
|
||||||
})
|
})
|
||||||
|
// disable diagnostics
|
||||||
|
metamaskController.diagnostics = null
|
||||||
|
// add sinon method spies
|
||||||
sandbox.spy(metamaskController.keyringController, 'createNewVaultAndKeychain')
|
sandbox.spy(metamaskController.keyringController, 'createNewVaultAndKeychain')
|
||||||
sandbox.spy(metamaskController.keyringController, 'createNewVaultAndRestore')
|
sandbox.spy(metamaskController.keyringController, 'createNewVaultAndRestore')
|
||||||
})
|
})
|
||||||
|
@ -14,18 +14,6 @@ describe('notice-controller', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('notices', function () {
|
describe('notices', function () {
|
||||||
describe('#getNoticesList', function () {
|
|
||||||
it('should return an empty array when new', function (done) {
|
|
||||||
// const testList = [{
|
|
||||||
// id: 0,
|
|
||||||
// read: false,
|
|
||||||
// title: 'Futuristic Notice',
|
|
||||||
// }]
|
|
||||||
var result = noticeController.getNoticesList()
|
|
||||||
assert.equal(result.length, 0)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#setNoticesList', function () {
|
describe('#setNoticesList', function () {
|
||||||
it('should set data appropriately', function (done) {
|
it('should set data appropriately', function (done) {
|
||||||
@ -41,36 +29,6 @@ describe('notice-controller', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#updateNoticeslist', function () {
|
|
||||||
it('should integrate the latest changes from the source', function (done) {
|
|
||||||
var testList = [{
|
|
||||||
id: 55,
|
|
||||||
read: false,
|
|
||||||
title: 'Futuristic Notice',
|
|
||||||
}]
|
|
||||||
noticeController.setNoticesList(testList)
|
|
||||||
noticeController.updateNoticesList().then(() => {
|
|
||||||
var newList = noticeController.getNoticesList()
|
|
||||||
assert.ok(newList[0].id === 55)
|
|
||||||
assert.ok(newList[1])
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
it('should not overwrite any existing fields', function (done) {
|
|
||||||
var testList = [{
|
|
||||||
id: 0,
|
|
||||||
read: false,
|
|
||||||
title: 'Futuristic Notice',
|
|
||||||
}]
|
|
||||||
noticeController.setNoticesList(testList)
|
|
||||||
var newList = noticeController.getNoticesList()
|
|
||||||
assert.equal(newList[0].id, 0)
|
|
||||||
assert.equal(newList[0].title, 'Futuristic Notice')
|
|
||||||
assert.equal(newList.length, 1)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#markNoticeRead', function () {
|
describe('#markNoticeRead', function () {
|
||||||
it('should mark a notice as read', function (done) {
|
it('should mark a notice as read', function (done) {
|
||||||
var testList = [{
|
var testList = [{
|
||||||
@ -86,7 +44,7 @@ describe('notice-controller', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#getLatestUnreadNotice', function () {
|
describe('#getNextUnreadNotice', function () {
|
||||||
it('should retrieve the latest unread notice', function (done) {
|
it('should retrieve the latest unread notice', function (done) {
|
||||||
var testList = [
|
var testList = [
|
||||||
{id: 0, read: true, title: 'Past Notice'},
|
{id: 0, read: true, title: 'Past Notice'},
|
||||||
@ -94,8 +52,8 @@ describe('notice-controller', function () {
|
|||||||
{id: 2, read: false, title: 'Future Notice'},
|
{id: 2, read: false, title: 'Future Notice'},
|
||||||
]
|
]
|
||||||
noticeController.setNoticesList(testList)
|
noticeController.setNoticesList(testList)
|
||||||
var latestUnread = noticeController.getLatestUnreadNotice()
|
var latestUnread = noticeController.getNextUnreadNotice()
|
||||||
assert.equal(latestUnread.id, 2)
|
assert.equal(latestUnread.id, 1)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
it('should return undefined if no unread notices exist.', function (done) {
|
it('should return undefined if no unread notices exist.', function (done) {
|
||||||
@ -105,7 +63,7 @@ describe('notice-controller', function () {
|
|||||||
{id: 2, read: true, title: 'Future Notice'},
|
{id: 2, read: true, title: 'Future Notice'},
|
||||||
]
|
]
|
||||||
noticeController.setNoticesList(testList)
|
noticeController.setNoticesList(testList)
|
||||||
var latestUnread = noticeController.getLatestUnreadNotice()
|
var latestUnread = noticeController.getNextUnreadNotice()
|
||||||
assert.ok(!latestUnread)
|
assert.ok(!latestUnread)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
const recipientBlackListChecker = require('../../../../../app/scripts/controllers/transactions/lib/recipient-blacklist-checker')
|
||||||
|
const {
|
||||||
|
ROPSTEN_CODE,
|
||||||
|
RINKEYBY_CODE,
|
||||||
|
KOVAN_CODE,
|
||||||
|
} = require('../../../../../app/scripts/controllers/network/enums')
|
||||||
|
|
||||||
|
const KeyringController = require('eth-keyring-controller')
|
||||||
|
|
||||||
|
describe('Recipient Blacklist Checker', function () {
|
||||||
|
|
||||||
|
let publicAccounts
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
const damnedMnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat'
|
||||||
|
const keyringController = new KeyringController({})
|
||||||
|
const Keyring = keyringController.getKeyringClassForType('HD Key Tree')
|
||||||
|
const opts = {
|
||||||
|
mnemonic: damnedMnemonic,
|
||||||
|
numberOfAccounts: 10,
|
||||||
|
}
|
||||||
|
const keyring = new Keyring(opts)
|
||||||
|
publicAccounts = await keyring.getAccounts()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#checkAccount', function () {
|
||||||
|
it('does not fail on test networks', function () {
|
||||||
|
let callCount = 0
|
||||||
|
const networks = [ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE]
|
||||||
|
for (let networkId in networks) {
|
||||||
|
publicAccounts.forEach((account) => {
|
||||||
|
recipientBlackListChecker.checkAccount(networkId, account)
|
||||||
|
callCount++
|
||||||
|
})
|
||||||
|
}
|
||||||
|
assert.equal(callCount, 30)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fails on mainnet', function () {
|
||||||
|
const mainnetId = 1
|
||||||
|
let callCount = 0
|
||||||
|
publicAccounts.forEach((account) => {
|
||||||
|
try {
|
||||||
|
recipientBlackListChecker.checkAccount(mainnetId, account)
|
||||||
|
assert.fail('function should have thrown an error')
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.message, 'Recipient is a public account')
|
||||||
|
}
|
||||||
|
callCount++
|
||||||
|
})
|
||||||
|
assert.equal(callCount, 10)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fails for public account - uppercase', function () {
|
||||||
|
const mainnetId = 1
|
||||||
|
const publicAccount = '0X0D1D4E623D10F9FBA5DB95830F7D3839406C6AF2'
|
||||||
|
try {
|
||||||
|
recipientBlackListChecker.checkAccount(mainnetId, publicAccount)
|
||||||
|
assert.fail('function should have thrown an error')
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.message, 'Recipient is a public account')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fails for public account - lowercase', async function () {
|
||||||
|
const mainnetId = 1
|
||||||
|
const publicAccount = '0x0d1d4e623d10f9fba5db95830f7d3839406c6af2'
|
||||||
|
try {
|
||||||
|
await recipientBlackListChecker.checkAccount(mainnetId, publicAccount)
|
||||||
|
assert.fail('function should have thrown an error')
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.message, 'Recipient is a public account')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -185,6 +185,23 @@ describe('Transaction Controller', function () {
|
|||||||
.catch(done)
|
.catch(done)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should fail if recipient is public', function (done) {
|
||||||
|
txController.networkStore = new ObservableStore(1)
|
||||||
|
txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d', to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' })
|
||||||
|
.catch((err) => {
|
||||||
|
if (err.message === 'Recipient is a public account') done()
|
||||||
|
else done(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not fail if recipient is public but not on mainnet', function (done) {
|
||||||
|
txController.once('newUnapprovedTx', (txMetaFromEmit) => {
|
||||||
|
assert(txMetaFromEmit, 'txMeta is falsey')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d', to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' })
|
||||||
|
.catch(done)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#addTxGasDefaults', function () {
|
describe('#addTxGasDefaults', function () {
|
||||||
|
17
test/unit/test-utils.js
Normal file
17
test/unit/test-utils.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
assertRejects,
|
||||||
|
}
|
||||||
|
|
||||||
|
// assert.rejects added in node v10
|
||||||
|
async function assertRejects (asyncFn, regExp) {
|
||||||
|
let f = () => {}
|
||||||
|
try {
|
||||||
|
await asyncFn()
|
||||||
|
} catch (error) {
|
||||||
|
f = () => { throw error }
|
||||||
|
} finally {
|
||||||
|
assert.throws(f, regExp)
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,12 @@ const abi = require('human-standard-token-abi')
|
|||||||
const pify = require('pify')
|
const pify = require('pify')
|
||||||
const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url')
|
const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url')
|
||||||
const { getTokenAddressFromTokenObject } = require('./util')
|
const { getTokenAddressFromTokenObject } = require('./util')
|
||||||
|
const {
|
||||||
|
calcGasTotal,
|
||||||
|
calcTokenBalance,
|
||||||
|
estimateGas,
|
||||||
|
estimateGasPriceFromRecentBlocks,
|
||||||
|
} = require('./components/send_/send.utils')
|
||||||
const ethUtil = require('ethereumjs-util')
|
const ethUtil = require('ethereumjs-util')
|
||||||
const { fetchLocale } = require('../i18n-helper')
|
const { fetchLocale } = require('../i18n-helper')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
@ -155,8 +161,6 @@ var actions = {
|
|||||||
updateTransactionParams,
|
updateTransactionParams,
|
||||||
UPDATE_TRANSACTION_PARAMS: 'UPDATE_TRANSACTION_PARAMS',
|
UPDATE_TRANSACTION_PARAMS: 'UPDATE_TRANSACTION_PARAMS',
|
||||||
// send screen
|
// send screen
|
||||||
estimateGas,
|
|
||||||
getGasPrice,
|
|
||||||
UPDATE_GAS_LIMIT: 'UPDATE_GAS_LIMIT',
|
UPDATE_GAS_LIMIT: 'UPDATE_GAS_LIMIT',
|
||||||
UPDATE_GAS_PRICE: 'UPDATE_GAS_PRICE',
|
UPDATE_GAS_PRICE: 'UPDATE_GAS_PRICE',
|
||||||
UPDATE_GAS_TOTAL: 'UPDATE_GAS_TOTAL',
|
UPDATE_GAS_TOTAL: 'UPDATE_GAS_TOTAL',
|
||||||
@ -169,17 +173,21 @@ var actions = {
|
|||||||
UPDATE_MAX_MODE: 'UPDATE_MAX_MODE',
|
UPDATE_MAX_MODE: 'UPDATE_MAX_MODE',
|
||||||
UPDATE_SEND: 'UPDATE_SEND',
|
UPDATE_SEND: 'UPDATE_SEND',
|
||||||
CLEAR_SEND: 'CLEAR_SEND',
|
CLEAR_SEND: 'CLEAR_SEND',
|
||||||
updateGasLimit,
|
OPEN_FROM_DROPDOWN: 'OPEN_FROM_DROPDOWN',
|
||||||
updateGasPrice,
|
CLOSE_FROM_DROPDOWN: 'CLOSE_FROM_DROPDOWN',
|
||||||
updateGasTotal,
|
setGasLimit,
|
||||||
|
setGasPrice,
|
||||||
|
updateGasData,
|
||||||
|
setGasTotal,
|
||||||
|
setSendTokenBalance,
|
||||||
updateSendTokenBalance,
|
updateSendTokenBalance,
|
||||||
updateSendFrom,
|
updateSendFrom,
|
||||||
updateSendTo,
|
updateSendTo,
|
||||||
updateSendAmount,
|
updateSendAmount,
|
||||||
updateSendMemo,
|
updateSendMemo,
|
||||||
updateSendErrors,
|
|
||||||
setMaxModeTo,
|
setMaxModeTo,
|
||||||
updateSend,
|
updateSend,
|
||||||
|
updateSendErrors,
|
||||||
clearSend,
|
clearSend,
|
||||||
setSelectedAddress,
|
setSelectedAddress,
|
||||||
// app messages
|
// app messages
|
||||||
@ -550,10 +558,12 @@ function importNewAccount (strategy, args) {
|
|||||||
}
|
}
|
||||||
dispatch(actions.hideLoadingIndication())
|
dispatch(actions.hideLoadingIndication())
|
||||||
dispatch(actions.updateMetamaskState(newState))
|
dispatch(actions.updateMetamaskState(newState))
|
||||||
dispatch({
|
if (newState.selectedAddress) {
|
||||||
type: actions.SHOW_ACCOUNT_DETAIL,
|
dispatch({
|
||||||
value: newState.selectedAddress,
|
type: actions.SHOW_ACCOUNT_DETAIL,
|
||||||
})
|
value: newState.selectedAddress,
|
||||||
|
})
|
||||||
|
}
|
||||||
return newState
|
return newState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -701,60 +711,96 @@ function signTx (txData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function estimateGas (params = {}) {
|
function setGasLimit (gasLimit) {
|
||||||
return (dispatch) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
global.ethQuery.estimateGas(params, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
dispatch(actions.displayWarning(err.message))
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
dispatch(actions.hideWarning())
|
|
||||||
dispatch(actions.updateGasLimit(data))
|
|
||||||
return resolve(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateGasLimit (gasLimit) {
|
|
||||||
return {
|
return {
|
||||||
type: actions.UPDATE_GAS_LIMIT,
|
type: actions.UPDATE_GAS_LIMIT,
|
||||||
value: gasLimit,
|
value: gasLimit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGasPrice () {
|
function setGasPrice (gasPrice) {
|
||||||
return (dispatch) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
global.ethQuery.gasPrice((err, data) => {
|
|
||||||
if (err) {
|
|
||||||
dispatch(actions.displayWarning(err.message))
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
dispatch(actions.hideWarning())
|
|
||||||
dispatch(actions.updateGasPrice(data))
|
|
||||||
return resolve(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateGasPrice (gasPrice) {
|
|
||||||
return {
|
return {
|
||||||
type: actions.UPDATE_GAS_PRICE,
|
type: actions.UPDATE_GAS_PRICE,
|
||||||
value: gasPrice,
|
value: gasPrice,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateGasTotal (gasTotal) {
|
function setGasTotal (gasTotal) {
|
||||||
return {
|
return {
|
||||||
type: actions.UPDATE_GAS_TOTAL,
|
type: actions.UPDATE_GAS_TOTAL,
|
||||||
value: gasTotal,
|
value: gasTotal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSendTokenBalance (tokenBalance) {
|
function updateGasData ({
|
||||||
|
blockGasLimit,
|
||||||
|
recentBlocks,
|
||||||
|
selectedAddress,
|
||||||
|
selectedToken,
|
||||||
|
to,
|
||||||
|
value,
|
||||||
|
}) {
|
||||||
|
const estimatedGasPrice = estimateGasPriceFromRecentBlocks(recentBlocks)
|
||||||
|
return (dispatch) => {
|
||||||
|
return Promise.all([
|
||||||
|
Promise.resolve(estimatedGasPrice),
|
||||||
|
estimateGas({
|
||||||
|
estimateGasMethod: background.estimateGas,
|
||||||
|
blockGasLimit,
|
||||||
|
selectedAddress,
|
||||||
|
selectedToken,
|
||||||
|
to,
|
||||||
|
value,
|
||||||
|
gasPrice: estimatedGasPrice,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
.then(([gasPrice, gas]) => {
|
||||||
|
dispatch(actions.setGasPrice(gasPrice))
|
||||||
|
dispatch(actions.setGasLimit(gas))
|
||||||
|
return calcGasTotal(gas, gasPrice)
|
||||||
|
})
|
||||||
|
.then((gasEstimate) => {
|
||||||
|
dispatch(actions.setGasTotal(gasEstimate))
|
||||||
|
dispatch(updateSendErrors({ gasLoadingError: null }))
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
log.error(err)
|
||||||
|
dispatch(updateSendErrors({ gasLoadingError: 'gasLoadingError' }))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSendTokenBalance ({
|
||||||
|
selectedToken,
|
||||||
|
tokenContract,
|
||||||
|
address,
|
||||||
|
}) {
|
||||||
|
return (dispatch) => {
|
||||||
|
const tokenBalancePromise = tokenContract
|
||||||
|
? tokenContract.balanceOf(address)
|
||||||
|
: Promise.resolve()
|
||||||
|
return tokenBalancePromise
|
||||||
|
.then(usersToken => {
|
||||||
|
if (usersToken) {
|
||||||
|
const newTokenBalance = calcTokenBalance({ selectedToken, usersToken })
|
||||||
|
dispatch(setSendTokenBalance(newTokenBalance.toString(10)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
log.error(err)
|
||||||
|
updateSendErrors({ tokenBalance: 'tokenBalanceError' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSendErrors (errorObject) {
|
||||||
|
return {
|
||||||
|
type: actions.UPDATE_SEND_ERRORS,
|
||||||
|
value: errorObject,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSendTokenBalance (tokenBalance) {
|
||||||
return {
|
return {
|
||||||
type: actions.UPDATE_SEND_TOKEN_BALANCE,
|
type: actions.UPDATE_SEND_TOKEN_BALANCE,
|
||||||
value: tokenBalance,
|
value: tokenBalance,
|
||||||
@ -789,13 +835,6 @@ function updateSendMemo (memo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSendErrors (error) {
|
|
||||||
return {
|
|
||||||
type: actions.UPDATE_SEND_ERRORS,
|
|
||||||
value: error,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setMaxModeTo (bool) {
|
function setMaxModeTo (bool) {
|
||||||
return {
|
return {
|
||||||
type: actions.UPDATE_MAX_MODE,
|
type: actions.UPDATE_MAX_MODE,
|
||||||
|
@ -11,7 +11,7 @@ const log = require('loglevel')
|
|||||||
// init
|
// init
|
||||||
const InitializeScreen = require('../../mascara/src/app/first-time').default
|
const InitializeScreen = require('../../mascara/src/app/first-time').default
|
||||||
// accounts
|
// accounts
|
||||||
const SendTransactionScreen2 = require('./components/send/send-v2-container')
|
const SendTransactionScreen = require('./components/send_/send.container')
|
||||||
const ConfirmTxScreen = require('./conf-tx')
|
const ConfirmTxScreen = require('./conf-tx')
|
||||||
|
|
||||||
// slideout menu
|
// slideout menu
|
||||||
@ -77,7 +77,7 @@ class App extends Component {
|
|||||||
h(Authenticated, { path: SETTINGS_ROUTE, component: Settings }),
|
h(Authenticated, { path: SETTINGS_ROUTE, component: Settings }),
|
||||||
h(Authenticated, { path: NOTICE_ROUTE, exact, component: NoticeScreen }),
|
h(Authenticated, { path: NOTICE_ROUTE, exact, component: NoticeScreen }),
|
||||||
h(Authenticated, { path: `${CONFIRM_TRANSACTION_ROUTE}/:id?`, component: ConfirmTxScreen }),
|
h(Authenticated, { path: `${CONFIRM_TRANSACTION_ROUTE}/:id?`, component: ConfirmTxScreen }),
|
||||||
h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }),
|
h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen }),
|
||||||
h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
|
h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
|
||||||
h(Authenticated, { path: CONFIRM_ADD_TOKEN_ROUTE, exact, component: ConfirmAddTokenPage }),
|
h(Authenticated, { path: CONFIRM_ADD_TOKEN_ROUTE, exact, component: ConfirmAddTokenPage }),
|
||||||
h(Authenticated, { path: NEW_ACCOUNT_ROUTE, component: CreateAccountPage }),
|
h(Authenticated, { path: NEW_ACCOUNT_ROUTE, component: CreateAccountPage }),
|
||||||
@ -99,7 +99,7 @@ class App extends Component {
|
|||||||
} = this.props
|
} = this.props
|
||||||
const isLoadingNetwork = network === 'loading' && currentView.name !== 'config'
|
const isLoadingNetwork = network === 'loading' && currentView.name !== 'config'
|
||||||
const loadMessage = loadingMessage || isLoadingNetwork ?
|
const loadMessage = loadingMessage || isLoadingNetwork ?
|
||||||
this.getConnectingLabel() : null
|
this.getConnectingLabel(loadingMessage) : null
|
||||||
log.debug('Main ui render function')
|
log.debug('Main ui render function')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -210,7 +210,10 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getConnectingLabel = function () {
|
getConnectingLabel = function (loadingMessage) {
|
||||||
|
if (loadingMessage) {
|
||||||
|
return loadingMessage
|
||||||
|
}
|
||||||
const { provider } = this.props
|
const { provider } = this.props
|
||||||
const providerName = provider.type
|
const providerName = provider.type
|
||||||
|
|
||||||
@ -311,7 +314,7 @@ function mapStateToProps (state) {
|
|||||||
noActiveNotices,
|
noActiveNotices,
|
||||||
seedWords,
|
seedWords,
|
||||||
unapprovedTxs,
|
unapprovedTxs,
|
||||||
lastUnreadNotice,
|
nextUnreadNotice,
|
||||||
lostAccounts,
|
lostAccounts,
|
||||||
unapprovedMsgCount,
|
unapprovedMsgCount,
|
||||||
unapprovedPersonalMsgCount,
|
unapprovedPersonalMsgCount,
|
||||||
@ -345,7 +348,7 @@ function mapStateToProps (state) {
|
|||||||
network: state.metamask.network,
|
network: state.metamask.network,
|
||||||
provider: state.metamask.provider,
|
provider: state.metamask.provider,
|
||||||
forgottenPassword: state.appState.forgottenPassword,
|
forgottenPassword: state.appState.forgottenPassword,
|
||||||
lastUnreadNotice,
|
nextUnreadNotice,
|
||||||
lostAccounts,
|
lostAccounts,
|
||||||
frequentRpcList: state.metamask.frequentRpcList || [],
|
frequentRpcList: state.metamask.frequentRpcList || [],
|
||||||
currentCurrency: state.metamask.currentCurrency,
|
currentCurrency: state.metamask.currentCurrency,
|
||||||
|
@ -135,11 +135,12 @@ AccountMenu.prototype.renderAccounts = function () {
|
|||||||
showAccountDetail,
|
showAccountDetail,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
return Object.keys(identities).map((key, index) => {
|
const accountOrder = keyrings.reduce((list, keyring) => list.concat(keyring.accounts), [])
|
||||||
const identity = identities[key]
|
return accountOrder.map((address) => {
|
||||||
|
const identity = identities[address]
|
||||||
const isSelected = identity.address === selectedAddress
|
const isSelected = identity.address === selectedAddress
|
||||||
|
|
||||||
const balanceValue = accounts[key] ? accounts[key].balance : ''
|
const balanceValue = accounts[address] ? accounts[address].balance : ''
|
||||||
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...'
|
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...'
|
||||||
const simpleAddress = identity.address.substring(2).toLowerCase()
|
const simpleAddress = identity.address.substring(2).toLowerCase()
|
||||||
|
|
||||||
|
@ -1,113 +0,0 @@
|
|||||||
const Component = require('react').Component
|
|
||||||
const h = require('react-hyperscript')
|
|
||||||
const inherits = require('util').inherits
|
|
||||||
|
|
||||||
module.exports = CurrencyInput
|
|
||||||
|
|
||||||
inherits(CurrencyInput, Component)
|
|
||||||
function CurrencyInput (props) {
|
|
||||||
Component.call(this)
|
|
||||||
|
|
||||||
const sanitizedValue = sanitizeValue(props.value)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
value: sanitizedValue,
|
|
||||||
emptyState: false,
|
|
||||||
focused: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeNonDigits (str) {
|
|
||||||
return str.match(/\d|$/g).join('')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removes characters that are not digits, then removes leading zeros
|
|
||||||
function sanitizeInteger (val) {
|
|
||||||
return String(parseInt(removeNonDigits(val) || '0', 10))
|
|
||||||
}
|
|
||||||
|
|
||||||
function sanitizeDecimal (val) {
|
|
||||||
return removeNonDigits(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take a single string param and returns a non-negative integer or float as a string.
|
|
||||||
// Breaks the input into three parts: the integer, the decimal point, and the decimal/fractional part.
|
|
||||||
// Removes leading zeros from the integer, and non-digits from the integer and decimal
|
|
||||||
// The integer is returned as '0' in cases where it would be empty. A decimal point is
|
|
||||||
// included in the returned string if one is included in the param
|
|
||||||
// Examples:
|
|
||||||
// sanitizeValue('0') -> '0'
|
|
||||||
// sanitizeValue('a') -> '0'
|
|
||||||
// sanitizeValue('010.') -> '10.'
|
|
||||||
// sanitizeValue('0.005') -> '0.005'
|
|
||||||
// sanitizeValue('22.200') -> '22.200'
|
|
||||||
// sanitizeValue('.200') -> '0.200'
|
|
||||||
// sanitizeValue('a.b.1.c,89.123') -> '0.189123'
|
|
||||||
function sanitizeValue (value) {
|
|
||||||
let [ , integer, point, decimal] = (/([^.]*)([.]?)([^.]*)/).exec(value)
|
|
||||||
|
|
||||||
integer = sanitizeInteger(integer) || '0'
|
|
||||||
decimal = sanitizeDecimal(decimal)
|
|
||||||
|
|
||||||
return `${integer}${point}${decimal}`
|
|
||||||
}
|
|
||||||
|
|
||||||
CurrencyInput.prototype.handleChange = function (newValue) {
|
|
||||||
const { onInputChange } = this.props
|
|
||||||
const { value } = this.state
|
|
||||||
|
|
||||||
let parsedValue = newValue
|
|
||||||
const newValueLastIndex = newValue.length - 1
|
|
||||||
|
|
||||||
if (value === '0' && newValue[newValueLastIndex] === '0') {
|
|
||||||
parsedValue = parsedValue.slice(0, newValueLastIndex)
|
|
||||||
}
|
|
||||||
const sanitizedValue = sanitizeValue(parsedValue)
|
|
||||||
this.setState({
|
|
||||||
value: sanitizedValue,
|
|
||||||
emptyState: newValue === '' && sanitizedValue === '0',
|
|
||||||
})
|
|
||||||
onInputChange(sanitizedValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If state.value === props.value plus a decimal point, or at least one
|
|
||||||
// zero or a decimal point and at least one zero, then this returns state.value
|
|
||||||
// after it is sanitized with getValueParts
|
|
||||||
CurrencyInput.prototype.getValueToRender = function () {
|
|
||||||
const { value } = this.props
|
|
||||||
const { value: stateValue } = this.state
|
|
||||||
|
|
||||||
const trailingStateString = (new RegExp(`^${value}(.+)`)).exec(stateValue)
|
|
||||||
const trailingDecimalAndZeroes = trailingStateString && (/^[.0]0*/).test(trailingStateString[1])
|
|
||||||
|
|
||||||
return sanitizeValue(trailingDecimalAndZeroes
|
|
||||||
? stateValue
|
|
||||||
: value)
|
|
||||||
}
|
|
||||||
|
|
||||||
CurrencyInput.prototype.render = function () {
|
|
||||||
const {
|
|
||||||
className,
|
|
||||||
placeholder,
|
|
||||||
readOnly,
|
|
||||||
inputRef,
|
|
||||||
type,
|
|
||||||
} = this.props
|
|
||||||
const { emptyState, focused } = this.state
|
|
||||||
|
|
||||||
const inputSizeMultiplier = readOnly ? 1 : 1.2
|
|
||||||
|
|
||||||
const valueToRender = this.getValueToRender()
|
|
||||||
return h('input', {
|
|
||||||
className,
|
|
||||||
type,
|
|
||||||
value: emptyState ? '' : valueToRender,
|
|
||||||
placeholder: focused ? '' : placeholder,
|
|
||||||
size: valueToRender.length * inputSizeMultiplier,
|
|
||||||
readOnly,
|
|
||||||
onFocus: () => this.setState({ focused: true, emptyState: valueToRender === '0' }),
|
|
||||||
onBlur: () => this.setState({ focused: false, emptyState: false }),
|
|
||||||
onChange: e => this.handleChange(e.target.value),
|
|
||||||
ref: inputRef,
|
|
||||||
})
|
|
||||||
}
|
|
@ -8,15 +8,19 @@ const GasModalCard = require('./gas-modal-card')
|
|||||||
|
|
||||||
const ethUtil = require('ethereumjs-util')
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
|
||||||
|
import {
|
||||||
|
updateSendErrors,
|
||||||
|
} from '../../ducks/send.duck'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
MIN_GAS_PRICE_DEC,
|
MIN_GAS_PRICE_DEC,
|
||||||
MIN_GAS_LIMIT_DEC,
|
MIN_GAS_LIMIT_DEC,
|
||||||
MIN_GAS_PRICE_GWEI,
|
MIN_GAS_PRICE_GWEI,
|
||||||
} = require('../send/send-constants')
|
} = require('../send_/send.constants')
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isBalanceSufficient,
|
isBalanceSufficient,
|
||||||
} = require('../send/send-utils')
|
} = require('../send_/send.utils')
|
||||||
|
|
||||||
const {
|
const {
|
||||||
conversionUtil,
|
conversionUtil,
|
||||||
@ -61,11 +65,11 @@ function mapStateToProps (state) {
|
|||||||
function mapDispatchToProps (dispatch) {
|
function mapDispatchToProps (dispatch) {
|
||||||
return {
|
return {
|
||||||
hideModal: () => dispatch(actions.hideModal()),
|
hideModal: () => dispatch(actions.hideModal()),
|
||||||
updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)),
|
setGasPrice: newGasPrice => dispatch(actions.setGasPrice(newGasPrice)),
|
||||||
updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)),
|
setGasLimit: newGasLimit => dispatch(actions.setGasLimit(newGasLimit)),
|
||||||
updateGasTotal: newGasTotal => dispatch(actions.updateGasTotal(newGasTotal)),
|
setGasTotal: newGasTotal => dispatch(actions.setGasTotal(newGasTotal)),
|
||||||
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
|
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
|
||||||
updateSendErrors: error => dispatch(actions.updateSendErrors(error)),
|
updateSendErrors: error => dispatch(updateSendErrors(error)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,10 +109,10 @@ module.exports = connect(mapStateToProps, mapDispatchToProps)(CustomizeGasModal)
|
|||||||
|
|
||||||
CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
|
CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
|
||||||
const {
|
const {
|
||||||
updateGasPrice,
|
setGasPrice,
|
||||||
updateGasLimit,
|
setGasLimit,
|
||||||
hideModal,
|
hideModal,
|
||||||
updateGasTotal,
|
setGasTotal,
|
||||||
maxModeOn,
|
maxModeOn,
|
||||||
selectedToken,
|
selectedToken,
|
||||||
balance,
|
balance,
|
||||||
@ -125,9 +129,9 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
|
|||||||
updateSendAmount(maxAmount)
|
updateSendAmount(maxAmount)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGasPrice(ethUtil.addHexPrefix(gasPrice))
|
setGasPrice(ethUtil.addHexPrefix(gasPrice))
|
||||||
updateGasLimit(ethUtil.addHexPrefix(gasLimit))
|
setGasLimit(ethUtil.addHexPrefix(gasLimit))
|
||||||
updateGasTotal(ethUtil.addHexPrefix(gasTotal))
|
setGasTotal(ethUtil.addHexPrefix(gasTotal))
|
||||||
updateSendErrors({ insufficientFunds: false })
|
updateSendErrors({ insufficientFunds: false })
|
||||||
hideModal()
|
hideModal()
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const Component = require('react').Component
|
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 AccountListItem = require('../send/account-list-item')
|
const AccountListItem = require('../send_/account-list-item/account-list-item.component').default
|
||||||
|
|
||||||
module.exports = AccountDropdownMini
|
module.exports = AccountDropdownMini
|
||||||
|
|
||||||
|
@ -4,14 +4,21 @@ const h = require('react-hyperscript')
|
|||||||
const inherits = require('util').inherits
|
const inherits = require('util').inherits
|
||||||
const connect = require('react-redux').connect
|
const connect = require('react-redux').connect
|
||||||
const actions = require('../../actions')
|
const actions = require('../../actions')
|
||||||
|
const genAccountLink = require('etherscan-link').createAccountLink
|
||||||
|
const copyToClipboard = require('copy-to-clipboard')
|
||||||
|
const { Menu, Item, CloseArea } = require('./components/menu')
|
||||||
|
|
||||||
TokenMenuDropdown.contextTypes = {
|
TokenMenuDropdown.contextTypes = {
|
||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = connect(null, mapDispatchToProps)(TokenMenuDropdown)
|
module.exports = connect(mapStateToProps, mapDispatchToProps)(TokenMenuDropdown)
|
||||||
|
|
||||||
|
function mapStateToProps (state) {
|
||||||
|
return {
|
||||||
|
network: state.metamask.network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
function mapDispatchToProps (dispatch) {
|
||||||
return {
|
return {
|
||||||
@ -37,22 +44,34 @@ TokenMenuDropdown.prototype.onClose = function (e) {
|
|||||||
TokenMenuDropdown.prototype.render = function () {
|
TokenMenuDropdown.prototype.render = function () {
|
||||||
const { showHideTokenConfirmationModal } = this.props
|
const { showHideTokenConfirmationModal } = this.props
|
||||||
|
|
||||||
return h('div.token-menu-dropdown', {}, [
|
return h(Menu, { className: 'token-menu-dropdown', isShowing: true }, [
|
||||||
h('div.token-menu-dropdown__close-area', {
|
h(CloseArea, {
|
||||||
onClick: this.onClose,
|
onClick: this.onClose,
|
||||||
}),
|
}),
|
||||||
h('div.token-menu-dropdown__container', {}, [
|
h(Item, {
|
||||||
h('div.token-menu-dropdown__options', {}, [
|
onClick: (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
h('div.token-menu-dropdown__option', {
|
showHideTokenConfirmationModal(this.props.token)
|
||||||
onClick: (e) => {
|
this.props.onClose()
|
||||||
e.stopPropagation()
|
},
|
||||||
showHideTokenConfirmationModal(this.props.token)
|
text: this.context.t('hideToken'),
|
||||||
this.props.onClose()
|
}),
|
||||||
},
|
h(Item, {
|
||||||
}, this.context.t('hideToken')),
|
onClick: (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
]),
|
copyToClipboard(this.props.token.address)
|
||||||
]),
|
this.props.onClose()
|
||||||
|
},
|
||||||
|
text: this.context.t('copyContractAddress'),
|
||||||
|
}),
|
||||||
|
h(Item, {
|
||||||
|
onClick: (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
const url = genAccountLink(this.props.token.address, this.props.network)
|
||||||
|
global.platform.openWindow({ url })
|
||||||
|
this.props.onClose()
|
||||||
|
},
|
||||||
|
text: this.context.t('viewOnEtherscan'),
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
const Component = require('react').Component
|
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 CurrencyInput = require('./currency-input')
|
|
||||||
const {
|
const {
|
||||||
addCurrencies,
|
addCurrencies,
|
||||||
conversionGTE,
|
conversionGTE,
|
||||||
@ -51,14 +50,15 @@ InputNumber.prototype.render = function () {
|
|||||||
const { unitLabel, step = 1, placeholder, value = 0 } = this.props
|
const { unitLabel, step = 1, placeholder, value = 0 } = this.props
|
||||||
|
|
||||||
return h('div.customize-gas-input-wrapper', {}, [
|
return h('div.customize-gas-input-wrapper', {}, [
|
||||||
h(CurrencyInput, {
|
h('input', {
|
||||||
className: 'customize-gas-input',
|
className: 'customize-gas-input',
|
||||||
value,
|
value,
|
||||||
placeholder,
|
placeholder,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
onInputChange: newValue => {
|
onChange: e => {
|
||||||
this.setValue(newValue)
|
this.setValue(e.target.value)
|
||||||
},
|
},
|
||||||
|
min: 0,
|
||||||
}),
|
}),
|
||||||
h('span.gas-tooltip-input-detail', {}, [unitLabel]),
|
h('span.gas-tooltip-input-detail', {}, [unitLabel]),
|
||||||
h('div.gas-tooltip-input-arrows', {}, [
|
h('div.gas-tooltip-input-arrows', {}, [
|
||||||
|
1
ui/app/components/page-container/index.js
Normal file
1
ui/app/components/page-container/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './page-container.component'
|
@ -0,0 +1,18 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
export default class PageContainerContent extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div className="page-container__content">
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from './page-container-footer.component'
|
@ -0,0 +1,54 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import Button from '../../button'
|
||||||
|
|
||||||
|
export default class PageContainerFooter extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
cancelText: PropTypes.string,
|
||||||
|
onSubmit: PropTypes.func,
|
||||||
|
submitText: PropTypes.string,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
onCancel,
|
||||||
|
cancelText,
|
||||||
|
onSubmit,
|
||||||
|
submitText,
|
||||||
|
disabled,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="page-container__footer">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
large={true}
|
||||||
|
className="page-container__footer-button"
|
||||||
|
onClick={() => onCancel()}
|
||||||
|
>
|
||||||
|
{ cancelText || this.context.t('cancel') }
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
large={true}
|
||||||
|
className="page-container__footer-button"
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={e => onSubmit(e)}
|
||||||
|
>
|
||||||
|
{ submitText || this.context.t('next') }
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
export default class PageContainerHeader extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
title: PropTypes.string,
|
||||||
|
subtitle: PropTypes.string,
|
||||||
|
onClose: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { title, subtitle, onClose } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="page-container__header">
|
||||||
|
|
||||||
|
<div className="page-container__title">
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="page-container__subtitle">
|
||||||
|
{subtitle}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="page-container__header-close"
|
||||||
|
onClick={() => onClose()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from './page-container-header.component'
|
@ -0,0 +1,57 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
export default class PageContainerHeader extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
subtitle: PropTypes.string,
|
||||||
|
onClose: PropTypes.func,
|
||||||
|
showBackButton: PropTypes.bool,
|
||||||
|
onBackButtonClick: PropTypes.func,
|
||||||
|
backButtonStyles: PropTypes.object,
|
||||||
|
backButtonString: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
renderHeaderRow () {
|
||||||
|
const { showBackButton, onBackButtonClick, backButtonStyles, backButtonString } = this.props
|
||||||
|
|
||||||
|
return showBackButton && (
|
||||||
|
<div className="page-container__header-row">
|
||||||
|
<span
|
||||||
|
className="page-container__back-button"
|
||||||
|
onClick={onBackButtonClick}
|
||||||
|
style={backButtonStyles}
|
||||||
|
>
|
||||||
|
{ backButtonString || 'Back' }
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { title, subtitle, onClose } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="page-container__header">
|
||||||
|
|
||||||
|
{ this.renderHeaderRow() }
|
||||||
|
|
||||||
|
<div className="page-container__title">
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="page-container__subtitle">
|
||||||
|
{subtitle}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="page-container__header-close"
|
||||||
|
onClick={() => onClose()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
72
ui/app/components/page-container/page-container.component.js
Normal file
72
ui/app/components/page-container/page-container.component.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import PageContainerHeader from './page-container-header'
|
||||||
|
import PageContainerFooter from './page-container-footer'
|
||||||
|
|
||||||
|
export default class PageContainer extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
// PageContainerHeader props
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
subtitle: PropTypes.string,
|
||||||
|
onClose: PropTypes.func,
|
||||||
|
showBackButton: PropTypes.bool,
|
||||||
|
onBackButtonClick: PropTypes.func,
|
||||||
|
backButtonStyles: PropTypes.object,
|
||||||
|
backButtonString: PropTypes.string,
|
||||||
|
// Content props
|
||||||
|
ContentComponent: PropTypes.func,
|
||||||
|
contentComponentProps: PropTypes.object,
|
||||||
|
// PageContainerFooter props
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
cancelText: PropTypes.string,
|
||||||
|
onSubmit: PropTypes.func,
|
||||||
|
submitText: PropTypes.string,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
onClose,
|
||||||
|
showBackButton,
|
||||||
|
onBackButtonClick,
|
||||||
|
backButtonStyles,
|
||||||
|
backButtonString,
|
||||||
|
ContentComponent,
|
||||||
|
contentComponentProps,
|
||||||
|
onCancel,
|
||||||
|
cancelText,
|
||||||
|
onSubmit,
|
||||||
|
submitText,
|
||||||
|
disabled,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="page-container">
|
||||||
|
<PageContainerHeader
|
||||||
|
title={title}
|
||||||
|
subtitle={subtitle}
|
||||||
|
onClose={onClose}
|
||||||
|
showBackButton={showBackButton}
|
||||||
|
onBackButtonClick={onBackButtonClick}
|
||||||
|
backButtonStyles={backButtonStyles}
|
||||||
|
backButtonString={backButtonString}
|
||||||
|
/>
|
||||||
|
<div className="page-container__content">
|
||||||
|
<ContentComponent { ...contentComponentProps } />
|
||||||
|
</div>
|
||||||
|
<PageContainerFooter
|
||||||
|
onCancel={onCancel}
|
||||||
|
cancelText={cancelText}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
submitText={submitText}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -231,7 +231,7 @@ class AddToken extends Component {
|
|||||||
<div className="add-token__custom-token-form">
|
<div className="add-token__custom-token-form">
|
||||||
<TextField
|
<TextField
|
||||||
id="custom-address"
|
id="custom-address"
|
||||||
label="Token Address"
|
label={this.context.t('tokenAddress')}
|
||||||
type="text"
|
type="text"
|
||||||
value={customAddress}
|
value={customAddress}
|
||||||
onChange={e => this.handleCustomAddressChange(e.target.value)}
|
onChange={e => this.handleCustomAddressChange(e.target.value)}
|
||||||
@ -241,7 +241,7 @@ class AddToken extends Component {
|
|||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
id="custom-symbol"
|
id="custom-symbol"
|
||||||
label="Token Symbol"
|
label={this.context.t('tokenSymbol')}
|
||||||
type="text"
|
type="text"
|
||||||
value={customSymbol}
|
value={customSymbol}
|
||||||
onChange={e => this.handleCustomSymbolChange(e.target.value)}
|
onChange={e => this.handleCustomSymbolChange(e.target.value)}
|
||||||
@ -252,7 +252,7 @@ class AddToken extends Component {
|
|||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
id="custom-decimals"
|
id="custom-decimals"
|
||||||
label="Decimals of Precision"
|
label={this.context.t('decimal')}
|
||||||
type="number"
|
type="number"
|
||||||
value={customDecimals}
|
value={customDecimals}
|
||||||
onChange={e => this.handleCustomDecimalsChange(e.target.value)}
|
onChange={e => this.handleCustomDecimalsChange(e.target.value)}
|
||||||
|
@ -82,18 +82,19 @@ class JsonImportSubview extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createNewKeychain () {
|
createNewKeychain () {
|
||||||
|
const { firstAddress, displayWarning, importNewJsonAccount, setSelectedAddress } = this.props
|
||||||
const state = this.state
|
const state = this.state
|
||||||
|
|
||||||
if (!state) {
|
if (!state) {
|
||||||
const message = this.context.t('validFileImport')
|
const message = this.context.t('validFileImport')
|
||||||
return this.props.displayWarning(message)
|
return displayWarning(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { fileContents } = state
|
const { fileContents } = state
|
||||||
|
|
||||||
if (!fileContents) {
|
if (!fileContents) {
|
||||||
const message = this.context.t('needImportFile')
|
const message = this.context.t('needImportFile')
|
||||||
return this.props.displayWarning(message)
|
return displayWarning(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
const passwordInput = document.getElementById('json-password-box')
|
const passwordInput = document.getElementById('json-password-box')
|
||||||
@ -101,12 +102,19 @@ class JsonImportSubview extends Component {
|
|||||||
|
|
||||||
if (!password) {
|
if (!password) {
|
||||||
const message = this.context.t('needImportPassword')
|
const message = this.context.t('needImportPassword')
|
||||||
return this.props.displayWarning(message)
|
return displayWarning(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.importNewJsonAccount([ fileContents, password ])
|
importNewJsonAccount([ fileContents, password ])
|
||||||
// JS runtime requires caught rejections but failures are handled by Redux
|
.then(({ selectedAddress }) => {
|
||||||
.catch()
|
if (selectedAddress) {
|
||||||
|
history.push(DEFAULT_ROUTE)
|
||||||
|
} else {
|
||||||
|
displayWarning('Error importing account.')
|
||||||
|
setSelectedAddress(firstAddress)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => displayWarning(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,14 +122,17 @@ JsonImportSubview.propTypes = {
|
|||||||
error: PropTypes.string,
|
error: PropTypes.string,
|
||||||
goHome: PropTypes.func,
|
goHome: PropTypes.func,
|
||||||
displayWarning: PropTypes.func,
|
displayWarning: PropTypes.func,
|
||||||
|
firstAddress: PropTypes.string,
|
||||||
importNewJsonAccount: PropTypes.func,
|
importNewJsonAccount: PropTypes.func,
|
||||||
history: PropTypes.object,
|
history: PropTypes.object,
|
||||||
|
setSelectedAddress: PropTypes.func,
|
||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
return {
|
return {
|
||||||
error: state.appState.warning,
|
error: state.appState.warning,
|
||||||
|
firstAddress: Object.keys(state.metamask.accounts)[0],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,6 +141,7 @@ const mapDispatchToProps = dispatch => {
|
|||||||
goHome: () => dispatch(actions.goHome()),
|
goHome: () => dispatch(actions.goHome()),
|
||||||
displayWarning: warning => dispatch(actions.displayWarning(warning)),
|
displayWarning: warning => dispatch(actions.displayWarning(warning)),
|
||||||
importNewJsonAccount: options => dispatch(actions.importNewAccount('JSON File', options)),
|
importNewJsonAccount: options => dispatch(actions.importNewAccount('JSON File', options)),
|
||||||
|
setSelectedAddress: (address) => dispatch(actions.setSelectedAddress(address)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ module.exports = compose(
|
|||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
return {
|
return {
|
||||||
error: state.appState.warning,
|
error: state.appState.warning,
|
||||||
|
firstAddress: Object.keys(state.metamask.accounts)[0],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +30,8 @@ function mapDispatchToProps (dispatch) {
|
|||||||
importNewAccount: (strategy, [ privateKey ]) => {
|
importNewAccount: (strategy, [ privateKey ]) => {
|
||||||
return dispatch(actions.importNewAccount(strategy, [ privateKey ]))
|
return dispatch(actions.importNewAccount(strategy, [ privateKey ]))
|
||||||
},
|
},
|
||||||
displayWarning: () => dispatch(actions.displayWarning(null)),
|
displayWarning: (message) => dispatch(actions.displayWarning(message || null)),
|
||||||
|
setSelectedAddress: (address) => dispatch(actions.setSelectedAddress(address)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +42,7 @@ function PrivateKeyImportView () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PrivateKeyImportView.prototype.render = function () {
|
PrivateKeyImportView.prototype.render = function () {
|
||||||
const { error } = this.props
|
const { error, displayWarning } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
h('div.new-account-import-form__private-key', [
|
h('div.new-account-import-form__private-key', [
|
||||||
@ -60,7 +62,10 @@ PrivateKeyImportView.prototype.render = function () {
|
|||||||
h('div.new-account-import-form__buttons', {}, [
|
h('div.new-account-import-form__buttons', {}, [
|
||||||
|
|
||||||
h('button.btn-default.btn--large.new-account-create-form__button', {
|
h('button.btn-default.btn--large.new-account-create-form__button', {
|
||||||
onClick: () => this.props.history.push(DEFAULT_ROUTE),
|
onClick: () => {
|
||||||
|
displayWarning(null)
|
||||||
|
this.props.history.push(DEFAULT_ROUTE)
|
||||||
|
},
|
||||||
}, [
|
}, [
|
||||||
this.context.t('cancel'),
|
this.context.t('cancel'),
|
||||||
]),
|
]),
|
||||||
@ -88,10 +93,16 @@ 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
|
const { importNewAccount, history, displayWarning, setSelectedAddress, firstAddress } = this.props
|
||||||
|
|
||||||
importNewAccount('Private Key', [ privateKey ])
|
importNewAccount('Private Key', [ privateKey ])
|
||||||
// JS runtime requires caught rejections but failures are handled by Redux
|
.then(({ selectedAddress }) => {
|
||||||
.catch()
|
if (selectedAddress) {
|
||||||
.then(() => history.push(DEFAULT_ROUTE))
|
history.push(DEFAULT_ROUTE)
|
||||||
|
} else {
|
||||||
|
displayWarning('Error importing account.')
|
||||||
|
setSelectedAddress(firstAddress)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => displayWarning(err))
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,9 @@ class CreateAccountPage extends Component {
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
onClick: () => history.push(NEW_ACCOUNT_ROUTE),
|
onClick: () => history.push(NEW_ACCOUNT_ROUTE),
|
||||||
}, 'Create'),
|
}, [
|
||||||
|
this.context.t('create'),
|
||||||
|
]),
|
||||||
|
|
||||||
h('div.new-account__tabs__tab', {
|
h('div.new-account__tabs__tab', {
|
||||||
className: classnames('new-account__tabs__tab', {
|
className: classnames('new-account__tabs__tab', {
|
||||||
@ -31,14 +33,16 @@ class CreateAccountPage extends Component {
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
onClick: () => history.push(IMPORT_ACCOUNT_ROUTE),
|
onClick: () => history.push(IMPORT_ACCOUNT_ROUTE),
|
||||||
}, 'Import'),
|
}, [
|
||||||
|
this.context.t('import'),
|
||||||
|
]),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
return h('div.new-account', {}, [
|
return h('div.new-account', {}, [
|
||||||
h('div.new-account__header', [
|
h('div.new-account__header', [
|
||||||
h('div.new-account__title', 'New Account'),
|
h('div.new-account__title', this.context.t('newAccount') ),
|
||||||
this.renderTabs(),
|
this.renderTabs(),
|
||||||
]),
|
]),
|
||||||
h('div.new-account__form', [
|
h('div.new-account__form', [
|
||||||
@ -62,6 +66,11 @@ class CreateAccountPage extends Component {
|
|||||||
CreateAccountPage.propTypes = {
|
CreateAccountPage.propTypes = {
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
history: PropTypes.object,
|
history: PropTypes.object,
|
||||||
|
t: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateAccountPage.contextTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
@ -86,9 +86,9 @@ class Home extends Component {
|
|||||||
// if (!props.noActiveNotices) {
|
// if (!props.noActiveNotices) {
|
||||||
// log.debug('rendering notice screen for unread notices.')
|
// log.debug('rendering notice screen for unread notices.')
|
||||||
// return h(NoticeScreen, {
|
// return h(NoticeScreen, {
|
||||||
// notice: props.lastUnreadNotice,
|
// notice: props.nextUnreadNotice,
|
||||||
// key: 'NoticeScreen',
|
// key: 'NoticeScreen',
|
||||||
// onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
|
// onConfirm: () => props.dispatch(actions.markNoticeRead(props.nextUnreadNotice)),
|
||||||
// })
|
// })
|
||||||
// } else if (props.lostAccounts && props.lostAccounts.length > 0) {
|
// } else if (props.lostAccounts && props.lostAccounts.length > 0) {
|
||||||
// log.debug('rendering notice screen for lost accounts view.')
|
// log.debug('rendering notice screen for lost accounts view.')
|
||||||
@ -279,7 +279,7 @@ function mapStateToProps (state) {
|
|||||||
noActiveNotices,
|
noActiveNotices,
|
||||||
seedWords,
|
seedWords,
|
||||||
unapprovedTxs,
|
unapprovedTxs,
|
||||||
lastUnreadNotice,
|
nextUnreadNotice,
|
||||||
lostAccounts,
|
lostAccounts,
|
||||||
unapprovedMsgCount,
|
unapprovedMsgCount,
|
||||||
unapprovedPersonalMsgCount,
|
unapprovedPersonalMsgCount,
|
||||||
@ -313,7 +313,7 @@ function mapStateToProps (state) {
|
|||||||
network: state.metamask.network,
|
network: state.metamask.network,
|
||||||
provider: state.metamask.provider,
|
provider: state.metamask.provider,
|
||||||
forgottenPassword: state.appState.forgottenPassword,
|
forgottenPassword: state.appState.forgottenPassword,
|
||||||
lastUnreadNotice,
|
nextUnreadNotice,
|
||||||
lostAccounts,
|
lostAccounts,
|
||||||
frequentRpcList: state.metamask.frequentRpcList || [],
|
frequentRpcList: state.metamask.frequentRpcList || [],
|
||||||
currentCurrency: state.metamask.currentCurrency,
|
currentCurrency: state.metamask.currentCurrency,
|
||||||
|
@ -154,11 +154,11 @@ class Notice extends Component {
|
|||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const { metamask } = state
|
const { metamask } = state
|
||||||
const { noActiveNotices, lastUnreadNotice, lostAccounts } = metamask
|
const { noActiveNotices, nextUnreadNotice, lostAccounts } = metamask
|
||||||
|
|
||||||
return {
|
return {
|
||||||
noActiveNotices,
|
noActiveNotices,
|
||||||
lastUnreadNotice,
|
nextUnreadNotice,
|
||||||
lostAccounts,
|
lostAccounts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,21 +171,21 @@ Notice.propTypes = {
|
|||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
markNoticeRead: lastUnreadNotice => dispatch(actions.markNoticeRead(lastUnreadNotice)),
|
markNoticeRead: nextUnreadNotice => dispatch(actions.markNoticeRead(nextUnreadNotice)),
|
||||||
markAccountsFound: () => dispatch(actions.markAccountsFound()),
|
markAccountsFound: () => dispatch(actions.markAccountsFound()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||||
const { noActiveNotices, lastUnreadNotice, lostAccounts } = stateProps
|
const { noActiveNotices, nextUnreadNotice, lostAccounts } = stateProps
|
||||||
const { markNoticeRead, markAccountsFound } = dispatchProps
|
const { markNoticeRead, markAccountsFound } = dispatchProps
|
||||||
|
|
||||||
let notice
|
let notice
|
||||||
let onConfirm
|
let onConfirm
|
||||||
|
|
||||||
if (!noActiveNotices) {
|
if (!noActiveNotices) {
|
||||||
notice = lastUnreadNotice
|
notice = nextUnreadNotice
|
||||||
onConfirm = () => markNoticeRead(lastUnreadNotice)
|
onConfirm = () => markNoticeRead(nextUnreadNotice)
|
||||||
} else if (lostAccounts && lostAccounts.length > 0) {
|
} else if (lostAccounts && lostAccounts.length > 0) {
|
||||||
notice = generateLostAccountsNotice(lostAccounts)
|
notice = generateLostAccountsNotice(lostAccounts)
|
||||||
onConfirm = () => markAccountsFound()
|
onConfirm = () => markAccountsFound()
|
||||||
|
@ -14,8 +14,8 @@ class Config extends Component {
|
|||||||
return h('div.settings__tabs', [
|
return h('div.settings__tabs', [
|
||||||
h(TabBar, {
|
h(TabBar, {
|
||||||
tabs: [
|
tabs: [
|
||||||
{ content: 'Settings', key: SETTINGS_ROUTE },
|
{ content: this.context.t('settings'), key: SETTINGS_ROUTE },
|
||||||
{ content: 'Info', key: INFO_ROUTE },
|
{ content: this.context.t('info'), key: INFO_ROUTE },
|
||||||
],
|
],
|
||||||
isActive: key => matchPath(location.pathname, { path: key, exact: true }),
|
isActive: key => matchPath(location.pathname, { path: key, exact: true }),
|
||||||
onSelect: key => history.push(key),
|
onSelect: key => history.push(key),
|
||||||
@ -54,6 +54,11 @@ class Config extends Component {
|
|||||||
Config.propTypes = {
|
Config.propTypes = {
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
history: PropTypes.object,
|
history: PropTypes.object,
|
||||||
|
t: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
Config.contextTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Config
|
module.exports = Config
|
||||||
|
@ -120,7 +120,7 @@ class UnlockPage extends Component {
|
|||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
id="password"
|
id="password"
|
||||||
label="Password"
|
label={this.context.t('password')}
|
||||||
type="password"
|
type="password"
|
||||||
value={this.state.password}
|
value={this.state.password}
|
||||||
onChange={event => this.handleInputChange(event)}
|
onChange={event => this.handleInputChange(event)}
|
||||||
|
@ -11,7 +11,7 @@ const { conversionUtil } = require('../../conversion-util')
|
|||||||
const SenderToRecipient = require('../sender-to-recipient')
|
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')
|
||||||
|
|
||||||
class ConfirmDeployContract extends Component {
|
class ConfirmDeployContract extends Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
|
@ -17,22 +17,26 @@ const {
|
|||||||
multiplyCurrencies,
|
multiplyCurrencies,
|
||||||
} = require('../../conversion-util')
|
} = require('../../conversion-util')
|
||||||
const {
|
const {
|
||||||
getGasTotal,
|
calcGasTotal,
|
||||||
isBalanceSufficient,
|
isBalanceSufficient,
|
||||||
} = require('../send/send-utils')
|
} = require('../send_/send.utils')
|
||||||
const GasFeeDisplay = require('../send/gas-fee-display-v2')
|
const GasFeeDisplay = require('../send/gas-fee-display-v2')
|
||||||
const SenderToRecipient = require('../sender-to-recipient')
|
const SenderToRecipient = require('../sender-to-recipient')
|
||||||
const NetworkDisplay = require('../network-display')
|
const NetworkDisplay = require('../network-display')
|
||||||
const currencyFormatter = require('currency-formatter')
|
const currencyFormatter = require('currency-formatter')
|
||||||
const currencies = require('currency-formatter/currencies')
|
const currencies = require('currency-formatter/currencies')
|
||||||
|
|
||||||
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')
|
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
|
||||||
const {
|
const {
|
||||||
ENVIRONMENT_TYPE_POPUP,
|
ENVIRONMENT_TYPE_POPUP,
|
||||||
ENVIRONMENT_TYPE_NOTIFICATION,
|
ENVIRONMENT_TYPE_NOTIFICATION,
|
||||||
} = require('../../../../app/scripts/lib/enums')
|
} = require('../../../../app/scripts/lib/enums')
|
||||||
|
|
||||||
|
import {
|
||||||
|
updateSendErrors,
|
||||||
|
} from '../../ducks/send.duck'
|
||||||
|
|
||||||
ConfirmSendEther.contextTypes = {
|
ConfirmSendEther.contextTypes = {
|
||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
}
|
}
|
||||||
@ -109,7 +113,7 @@ function mapDispatchToProps (dispatch) {
|
|||||||
}))
|
}))
|
||||||
dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' }))
|
dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' }))
|
||||||
},
|
},
|
||||||
updateSendErrors: error => dispatch(actions.updateSendErrors(error)),
|
updateSendErrors: error => dispatch(updateSendErrors(error)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +149,7 @@ ConfirmSendEther.prototype.updateComponentSendErrors = function (prevProps) {
|
|||||||
if (shouldUpdateBalanceSendErrors) {
|
if (shouldUpdateBalanceSendErrors) {
|
||||||
const balanceIsSufficient = this.isBalanceSufficient(txMeta)
|
const balanceIsSufficient = this.isBalanceSufficient(txMeta)
|
||||||
updateSendErrors({
|
updateSendErrors({
|
||||||
insufficientFunds: balanceIsSufficient ? false : this.context.t('insufficientFunds'),
|
insufficientFunds: balanceIsSufficient ? false : 'insufficientFunds',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +157,7 @@ ConfirmSendEther.prototype.updateComponentSendErrors = function (prevProps) {
|
|||||||
|
|
||||||
if (shouldUpdateSimulationSendError) {
|
if (shouldUpdateSimulationSendError) {
|
||||||
updateSendErrors({
|
updateSendErrors({
|
||||||
simulationFails: !txMeta.simulationFails ? false : this.context.t('transactionError'),
|
simulationFails: !txMeta.simulationFails ? false : 'transactionError',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -585,9 +589,9 @@ ConfirmSendEther.prototype.onSubmit = function (event) {
|
|||||||
if (valid && this.verifyGasParams() && balanceIsSufficient) {
|
if (valid && this.verifyGasParams() && balanceIsSufficient) {
|
||||||
this.props.sendTransaction(txMeta, event)
|
this.props.sendTransaction(txMeta, event)
|
||||||
} else if (!balanceIsSufficient) {
|
} else if (!balanceIsSufficient) {
|
||||||
updateSendErrors({ insufficientFunds: this.context.t('insufficientFunds') })
|
updateSendErrors({ insufficientFunds: 'insufficientFunds' })
|
||||||
} else {
|
} else {
|
||||||
updateSendErrors({ invalidGasParams: this.context.t('invalidGasParams') })
|
updateSendErrors({ invalidGasParams: 'invalidGasParams' })
|
||||||
this.setState({ submitting: false })
|
this.setState({ submitting: false })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -612,7 +616,7 @@ ConfirmSendEther.prototype.isBalanceSufficient = function (txMeta) {
|
|||||||
value: amount,
|
value: amount,
|
||||||
},
|
},
|
||||||
} = txMeta
|
} = txMeta
|
||||||
const gasTotal = getGasTotal(gas, gasPrice)
|
const gasTotal = calcGasTotal(gas, gasPrice)
|
||||||
|
|
||||||
return isBalanceSufficient({
|
return isBalanceSufficient({
|
||||||
amount,
|
amount,
|
||||||
@ -643,7 +647,7 @@ ConfirmSendEther.prototype.gatherTxMeta = function () {
|
|||||||
const state = this.state
|
const state = this.state
|
||||||
const txData = clone(state.txData) || clone(props.txData)
|
const txData = clone(state.txData) || clone(props.txData)
|
||||||
|
|
||||||
const { gasPrice: sendGasPrice, gas: sendGasLimit } = props.send
|
const { gasPrice: sendGasPrice, gasLimit: sendGasLimit } = props.send
|
||||||
const {
|
const {
|
||||||
lastGasPrice,
|
lastGasPrice,
|
||||||
txParams: {
|
txParams: {
|
||||||
|
@ -21,9 +21,9 @@ const {
|
|||||||
addCurrencies,
|
addCurrencies,
|
||||||
} = require('../../conversion-util')
|
} = require('../../conversion-util')
|
||||||
const {
|
const {
|
||||||
getGasTotal,
|
calcGasTotal,
|
||||||
isBalanceSufficient,
|
isBalanceSufficient,
|
||||||
} = require('../send/send-utils')
|
} = require('../send_/send.utils')
|
||||||
const {
|
const {
|
||||||
calcTokenAmount,
|
calcTokenAmount,
|
||||||
} = require('../../token-util')
|
} = require('../../token-util')
|
||||||
@ -31,7 +31,7 @@ const classnames = require('classnames')
|
|||||||
const currencyFormatter = require('currency-formatter')
|
const currencyFormatter = require('currency-formatter')
|
||||||
const currencies = require('currency-formatter/currencies')
|
const currencies = require('currency-formatter/currencies')
|
||||||
|
|
||||||
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
|
const { MIN_GAS_PRICE_HEX } = require('../send_/send.constants')
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getTokenExchangeRate,
|
getTokenExchangeRate,
|
||||||
@ -40,6 +40,10 @@ const {
|
|||||||
} = require('../../selectors')
|
} = require('../../selectors')
|
||||||
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
|
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
|
||||||
|
|
||||||
|
import {
|
||||||
|
updateSendErrors,
|
||||||
|
} from '../../ducks/send.duck'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ENVIRONMENT_TYPE_POPUP,
|
ENVIRONMENT_TYPE_POPUP,
|
||||||
ENVIRONMENT_TYPE_NOTIFICATION,
|
ENVIRONMENT_TYPE_NOTIFICATION,
|
||||||
@ -109,7 +113,7 @@ function mapDispatchToProps (dispatch, ownProps) {
|
|||||||
to,
|
to,
|
||||||
amount: tokenAmountInHex,
|
amount: tokenAmountInHex,
|
||||||
errors: { to: null, amount: null },
|
errors: { to: null, amount: null },
|
||||||
editingTransactionId: id,
|
editingTransactionId: id && id.toString(),
|
||||||
token: ownProps.token,
|
token: ownProps.token,
|
||||||
}))
|
}))
|
||||||
dispatch(actions.showSendTokenPage())
|
dispatch(actions.showSendTokenPage())
|
||||||
@ -147,7 +151,7 @@ function mapDispatchToProps (dispatch, ownProps) {
|
|||||||
}))
|
}))
|
||||||
dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' }))
|
dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' }))
|
||||||
},
|
},
|
||||||
updateSendErrors: error => dispatch(actions.updateSendErrors(error)),
|
updateSendErrors: error => dispatch(updateSendErrors(error)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -589,9 +593,9 @@ ConfirmSendToken.prototype.onSubmit = function (event) {
|
|||||||
if (valid && this.verifyGasParams() && balanceIsSufficient) {
|
if (valid && this.verifyGasParams() && balanceIsSufficient) {
|
||||||
this.props.sendTransaction(txMeta, event)
|
this.props.sendTransaction(txMeta, event)
|
||||||
} else if (!balanceIsSufficient) {
|
} else if (!balanceIsSufficient) {
|
||||||
updateSendErrors({ insufficientFunds: this.context.t('insufficientFunds') })
|
updateSendErrors({ insufficientFunds: 'insufficientFunds' })
|
||||||
} else {
|
} else {
|
||||||
updateSendErrors({ invalidGasParams: this.context.t('invalidGasParams') })
|
updateSendErrors({ invalidGasParams: 'invalidGasParams' })
|
||||||
this.setState({ submitting: false })
|
this.setState({ submitting: false })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -607,7 +611,7 @@ ConfirmSendToken.prototype.isBalanceSufficient = function (txMeta) {
|
|||||||
gasPrice,
|
gasPrice,
|
||||||
},
|
},
|
||||||
} = txMeta
|
} = txMeta
|
||||||
const gasTotal = getGasTotal(gas, gasPrice)
|
const gasTotal = calcGasTotal(gas, gasPrice)
|
||||||
|
|
||||||
return isBalanceSufficient({
|
return isBalanceSufficient({
|
||||||
amount: '0',
|
amount: '0',
|
||||||
@ -647,7 +651,7 @@ ConfirmSendToken.prototype.gatherTxMeta = function () {
|
|||||||
const state = this.state
|
const state = this.state
|
||||||
const txData = clone(state.txData) || clone(props.txData)
|
const txData = clone(state.txData) || clone(props.txData)
|
||||||
|
|
||||||
const { gasPrice: sendGasPrice, gas: sendGasLimit } = props.send
|
const { gasPrice: sendGasPrice, gasLimit: sendGasLimit } = props.send
|
||||||
const {
|
const {
|
||||||
lastGasPrice,
|
lastGasPrice,
|
||||||
txParams: {
|
txParams: {
|
||||||
|
@ -1,74 +0,0 @@
|
|||||||
const Component = require('react').Component
|
|
||||||
const h = require('react-hyperscript')
|
|
||||||
const inherits = require('util').inherits
|
|
||||||
const connect = require('react-redux').connect
|
|
||||||
const { checksumAddress } = require('../../util')
|
|
||||||
const Identicon = require('../identicon')
|
|
||||||
const CurrencyDisplay = require('./currency-display')
|
|
||||||
const { conversionRateSelector, getCurrentCurrency } = require('../../selectors')
|
|
||||||
|
|
||||||
inherits(AccountListItem, Component)
|
|
||||||
function AccountListItem () {
|
|
||||||
Component.call(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
|
||||||
return {
|
|
||||||
conversionRate: conversionRateSelector(state),
|
|
||||||
currentCurrency: getCurrentCurrency(state),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(AccountListItem)
|
|
||||||
|
|
||||||
AccountListItem.prototype.render = function () {
|
|
||||||
const {
|
|
||||||
className,
|
|
||||||
account,
|
|
||||||
handleClick,
|
|
||||||
icon = null,
|
|
||||||
conversionRate,
|
|
||||||
currentCurrency,
|
|
||||||
displayBalance = true,
|
|
||||||
displayAddress = false,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
const { name, address, balance } = account || {}
|
|
||||||
|
|
||||||
return h('div.account-list-item', {
|
|
||||||
className,
|
|
||||||
onClick: () => handleClick({ name, address, balance }),
|
|
||||||
}, [
|
|
||||||
|
|
||||||
h('div.account-list-item__top-row', {}, [
|
|
||||||
|
|
||||||
h(
|
|
||||||
Identicon,
|
|
||||||
{
|
|
||||||
address,
|
|
||||||
diameter: 18,
|
|
||||||
className: 'account-list-item__identicon',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
h('div.account-list-item__account-name', {}, name || address),
|
|
||||||
|
|
||||||
icon && h('div.account-list-item__icon', [icon]),
|
|
||||||
|
|
||||||
]),
|
|
||||||
|
|
||||||
displayAddress && name && h('div.account-list-item__account-address', checksumAddress(address)),
|
|
||||||
|
|
||||||
displayBalance && h(CurrencyDisplay, {
|
|
||||||
primaryCurrency: 'ETH',
|
|
||||||
convertedCurrency: currentCurrency,
|
|
||||||
value: balance,
|
|
||||||
conversionRate,
|
|
||||||
readOnly: true,
|
|
||||||
className: 'account-list-item__account-balances',
|
|
||||||
primaryBalanceClassName: 'account-list-item__account-primary-balance',
|
|
||||||
convertedBalanceClassName: 'account-list-item__account-secondary-balance',
|
|
||||||
}, name),
|
|
||||||
|
|
||||||
])
|
|
||||||
}
|
|
@ -1,10 +1,10 @@
|
|||||||
const Component = require('react').Component
|
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 CurrencyInput = require('../currency-input')
|
|
||||||
const { conversionUtil, multiplyCurrencies } = require('../../conversion-util')
|
const { conversionUtil, multiplyCurrencies } = require('../../conversion-util')
|
||||||
const currencyFormatter = require('currency-formatter')
|
const currencyFormatter = require('currency-formatter')
|
||||||
const currencies = require('currency-formatter/currencies')
|
const currencies = require('currency-formatter/currencies')
|
||||||
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
|
||||||
module.exports = CurrencyDisplay
|
module.exports = CurrencyDisplay
|
||||||
|
|
||||||
@ -21,36 +21,52 @@ function toHexWei (value) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CurrencyDisplay.prototype.componentWillMount = function () {
|
||||||
|
this.setState({
|
||||||
|
valueToRender: this.getValueToRender(this.props),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrencyDisplay.prototype.componentWillReceiveProps = function (nextProps) {
|
||||||
|
const currentValueToRender = this.getValueToRender(this.props)
|
||||||
|
const newValueToRender = this.getValueToRender(nextProps)
|
||||||
|
if (currentValueToRender !== newValueToRender) {
|
||||||
|
this.setState({
|
||||||
|
valueToRender: newValueToRender,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CurrencyDisplay.prototype.getAmount = function (value) {
|
CurrencyDisplay.prototype.getAmount = function (value) {
|
||||||
const { selectedToken } = this.props
|
const { selectedToken } = this.props
|
||||||
const { decimals } = selectedToken || {}
|
const { decimals } = selectedToken || {}
|
||||||
const multiplier = Math.pow(10, Number(decimals || 0))
|
const multiplier = Math.pow(10, Number(decimals || 0))
|
||||||
|
|
||||||
const sendAmount = multiplyCurrencies(value, multiplier, {toNumericBase: 'hex'})
|
const sendAmount = multiplyCurrencies(value || '0', multiplier, {toNumericBase: 'hex'})
|
||||||
|
|
||||||
return selectedToken
|
return selectedToken
|
||||||
? sendAmount
|
? sendAmount
|
||||||
: toHexWei(value)
|
: toHexWei(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrencyDisplay.prototype.getValueToRender = function () {
|
CurrencyDisplay.prototype.getValueToRender = function ({ selectedToken, conversionRate, value, readOnly }) {
|
||||||
const { selectedToken, conversionRate, value } = this.props
|
if (value === '0x0') return readOnly ? '0' : ''
|
||||||
|
|
||||||
const { decimals, symbol } = selectedToken || {}
|
const { decimals, symbol } = selectedToken || {}
|
||||||
const multiplier = Math.pow(10, Number(decimals || 0))
|
const multiplier = Math.pow(10, Number(decimals || 0))
|
||||||
|
|
||||||
return selectedToken
|
return selectedToken
|
||||||
? conversionUtil(value, {
|
? conversionUtil(ethUtil.addHexPrefix(value), {
|
||||||
fromNumericBase: 'hex',
|
fromNumericBase: 'hex',
|
||||||
|
toNumericBase: 'dec',
|
||||||
toCurrency: symbol,
|
toCurrency: symbol,
|
||||||
conversionRate: multiplier,
|
conversionRate: multiplier,
|
||||||
invertConversionRate: true,
|
invertConversionRate: true,
|
||||||
})
|
})
|
||||||
: conversionUtil(value, {
|
: conversionUtil(ethUtil.addHexPrefix(value), {
|
||||||
fromNumericBase: 'hex',
|
fromNumericBase: 'hex',
|
||||||
toNumericBase: 'dec',
|
toNumericBase: 'dec',
|
||||||
fromDenomination: 'WEI',
|
fromDenomination: 'WEI',
|
||||||
numberOfDecimals: 6,
|
numberOfDecimals: 9,
|
||||||
conversionRate,
|
conversionRate,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -76,6 +92,22 @@ CurrencyDisplay.prototype.getConvertedValueToRender = function (nonFormattedValu
|
|||||||
: convertedValue
|
: convertedValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeLeadingZeroes (str) {
|
||||||
|
return str.replace(/^0*(?=\d)/, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrencyDisplay.prototype.handleChange = function (newVal) {
|
||||||
|
this.setState({ valueToRender: removeLeadingZeroes(newVal) })
|
||||||
|
this.props.onChange(this.getAmount(newVal))
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrencyDisplay.prototype.getInputWidth = function (valueToRender, readOnly) {
|
||||||
|
const valueString = String(valueToRender)
|
||||||
|
const valueLength = valueString.length || 1
|
||||||
|
const decimalPointDeficit = valueString.match(/\./) ? -0.5 : 0
|
||||||
|
return (valueLength + decimalPointDeficit + 0.75) + 'ch'
|
||||||
|
}
|
||||||
|
|
||||||
CurrencyDisplay.prototype.render = function () {
|
CurrencyDisplay.prototype.render = function () {
|
||||||
const {
|
const {
|
||||||
className = 'currency-display',
|
className = 'currency-display',
|
||||||
@ -85,10 +117,10 @@ CurrencyDisplay.prototype.render = function () {
|
|||||||
convertedCurrency,
|
convertedCurrency,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
inError = false,
|
inError = false,
|
||||||
handleChange,
|
onBlur,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
const { valueToRender } = this.state
|
||||||
|
|
||||||
const valueToRender = this.getValueToRender()
|
|
||||||
const convertedValueToRender = this.getConvertedValueToRender(valueToRender)
|
const convertedValueToRender = this.getConvertedValueToRender(valueToRender)
|
||||||
|
|
||||||
return h('div', {
|
return h('div', {
|
||||||
@ -96,24 +128,30 @@ CurrencyDisplay.prototype.render = function () {
|
|||||||
style: {
|
style: {
|
||||||
borderColor: inError ? 'red' : null,
|
borderColor: inError ? 'red' : null,
|
||||||
},
|
},
|
||||||
onClick: () => this.currencyInput && this.currencyInput.focus(),
|
onClick: () => {
|
||||||
|
this.currencyInput && this.currencyInput.focus()
|
||||||
|
},
|
||||||
}, [
|
}, [
|
||||||
|
|
||||||
h('div.currency-display__primary-row', [
|
h('div.currency-display__primary-row', [
|
||||||
|
|
||||||
h('div.currency-display__input-wrapper', [
|
h('div.currency-display__input-wrapper', [
|
||||||
|
|
||||||
h(readOnly ? 'input' : CurrencyInput, {
|
h('input', {
|
||||||
className: primaryBalanceClassName,
|
className: primaryBalanceClassName,
|
||||||
value: `${valueToRender}`,
|
value: `${valueToRender}`,
|
||||||
placeholder: '0',
|
placeholder: '0',
|
||||||
|
type: 'number',
|
||||||
readOnly,
|
readOnly,
|
||||||
...(!readOnly ? {
|
...(!readOnly ? {
|
||||||
onInputChange: newValue => {
|
onChange: e => this.handleChange(e.target.value),
|
||||||
handleChange(this.getAmount(newValue))
|
onBlur: () => onBlur(this.getAmount(valueToRender)),
|
||||||
},
|
|
||||||
inputRef: input => { this.currencyInput = input },
|
|
||||||
} : {}),
|
} : {}),
|
||||||
|
ref: input => { this.currencyInput = input },
|
||||||
|
style: {
|
||||||
|
width: this.getInputWidth(valueToRender, readOnly),
|
||||||
|
},
|
||||||
|
min: 0,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
h('span.currency-display__currency-symbol', primaryCurrency),
|
h('span.currency-display__currency-symbol', primaryCurrency),
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
const Component = require('react').Component
|
|
||||||
const h = require('react-hyperscript')
|
|
||||||
const inherits = require('util').inherits
|
|
||||||
const AccountListItem = require('./account-list-item')
|
|
||||||
|
|
||||||
module.exports = FromDropdown
|
|
||||||
|
|
||||||
inherits(FromDropdown, Component)
|
|
||||||
function FromDropdown () {
|
|
||||||
Component.call(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
FromDropdown.prototype.getListItemIcon = function (currentAccount, selectedAccount) {
|
|
||||||
const listItemIcon = h(`i.fa.fa-check.fa-lg`, { style: { color: '#02c9b1' } })
|
|
||||||
|
|
||||||
return currentAccount.address === selectedAccount.address
|
|
||||||
? listItemIcon
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
|
|
||||||
FromDropdown.prototype.renderDropdown = function () {
|
|
||||||
const {
|
|
||||||
accounts,
|
|
||||||
selectedAccount,
|
|
||||||
closeDropdown,
|
|
||||||
onSelect,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
return h('div', {}, [
|
|
||||||
|
|
||||||
h('div.send-v2__from-dropdown__close-area', {
|
|
||||||
onClick: closeDropdown,
|
|
||||||
}),
|
|
||||||
|
|
||||||
h('div.send-v2__from-dropdown__list', {}, [
|
|
||||||
|
|
||||||
...accounts.map(account => h(AccountListItem, {
|
|
||||||
className: 'account-list-item__dropdown',
|
|
||||||
account,
|
|
||||||
handleClick: () => {
|
|
||||||
onSelect(account)
|
|
||||||
closeDropdown()
|
|
||||||
},
|
|
||||||
icon: this.getListItemIcon(account, selectedAccount),
|
|
||||||
})),
|
|
||||||
|
|
||||||
]),
|
|
||||||
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
FromDropdown.prototype.render = function () {
|
|
||||||
const {
|
|
||||||
selectedAccount,
|
|
||||||
openDropdown,
|
|
||||||
dropdownOpen,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
return h('div.send-v2__from-dropdown', {}, [
|
|
||||||
|
|
||||||
h(AccountListItem, {
|
|
||||||
account: selectedAccount,
|
|
||||||
handleClick: openDropdown,
|
|
||||||
icon: h(`i.fa.fa-caret-down.fa-lg`, { style: { color: '#dedede' } }),
|
|
||||||
}),
|
|
||||||
|
|
||||||
dropdownOpen && this.renderDropdown(),
|
|
||||||
|
|
||||||
])
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
|||||||
const Component = require('react').Component
|
|
||||||
const PropTypes = require('prop-types')
|
|
||||||
const h = require('react-hyperscript')
|
|
||||||
const inherits = require('util').inherits
|
|
||||||
const InputNumber = require('../input-number.js')
|
|
||||||
const connect = require('react-redux').connect
|
|
||||||
|
|
||||||
GasTooltip.contextTypes = {
|
|
||||||
t: PropTypes.func,
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = connect()(GasTooltip)
|
|
||||||
|
|
||||||
|
|
||||||
inherits(GasTooltip, Component)
|
|
||||||
function GasTooltip () {
|
|
||||||
Component.call(this)
|
|
||||||
this.state = {
|
|
||||||
gasLimit: 0,
|
|
||||||
gasPrice: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateGasPrice = this.updateGasPrice.bind(this)
|
|
||||||
this.updateGasLimit = this.updateGasLimit.bind(this)
|
|
||||||
this.onClose = this.onClose.bind(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
GasTooltip.prototype.componentWillMount = function () {
|
|
||||||
const { gasPrice = 0, gasLimit = 0} = this.props
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
gasPrice: parseInt(gasPrice, 16) / 1000000000,
|
|
||||||
gasLimit: parseInt(gasLimit, 16),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
GasTooltip.prototype.updateGasPrice = function (newPrice) {
|
|
||||||
const { onFeeChange } = this.props
|
|
||||||
const { gasLimit } = this.state
|
|
||||||
|
|
||||||
this.setState({ gasPrice: newPrice })
|
|
||||||
onFeeChange({
|
|
||||||
gasLimit: gasLimit.toString(16),
|
|
||||||
gasPrice: (newPrice * 1000000000).toString(16),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
GasTooltip.prototype.updateGasLimit = function (newLimit) {
|
|
||||||
const { onFeeChange } = this.props
|
|
||||||
const { gasPrice } = this.state
|
|
||||||
|
|
||||||
this.setState({ gasLimit: newLimit })
|
|
||||||
onFeeChange({
|
|
||||||
gasLimit: newLimit.toString(16),
|
|
||||||
gasPrice: (gasPrice * 1000000000).toString(16),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
GasTooltip.prototype.onClose = function (e) {
|
|
||||||
e.stopPropagation()
|
|
||||||
this.props.onClose()
|
|
||||||
}
|
|
||||||
|
|
||||||
GasTooltip.prototype.render = function () {
|
|
||||||
const { gasPrice, gasLimit } = this.state
|
|
||||||
|
|
||||||
return h('div.gas-tooltip', {}, [
|
|
||||||
h('div.gas-tooltip-close-area', {
|
|
||||||
onClick: this.onClose,
|
|
||||||
}),
|
|
||||||
h('div.customize-gas-tooltip-container', {}, [
|
|
||||||
h('div.customize-gas-tooltip', {}, [
|
|
||||||
h('div.gas-tooltip-header.gas-tooltip-label', {}, ['Customize Gas']),
|
|
||||||
h('div.gas-tooltip-input-label', {}, [
|
|
||||||
h('span.gas-tooltip-label', {}, ['Gas Price']),
|
|
||||||
h('i.fa.fa-info-circle'),
|
|
||||||
]),
|
|
||||||
h(InputNumber, {
|
|
||||||
unitLabel: 'GWEI',
|
|
||||||
step: 1,
|
|
||||||
min: 0,
|
|
||||||
placeholder: '0',
|
|
||||||
value: gasPrice,
|
|
||||||
onChange: (newPrice) => this.updateGasPrice(newPrice),
|
|
||||||
}),
|
|
||||||
h('div.gas-tooltip-input-label', {
|
|
||||||
style: {
|
|
||||||
'marginTop': '81px',
|
|
||||||
},
|
|
||||||
}, [
|
|
||||||
h('span.gas-tooltip-label', {}, [this.context.t('gasLimit')]),
|
|
||||||
h('i.fa.fa-info-circle'),
|
|
||||||
]),
|
|
||||||
h(InputNumber, {
|
|
||||||
unitLabel: 'UNITS',
|
|
||||||
step: 1,
|
|
||||||
min: 0,
|
|
||||||
placeholder: '0',
|
|
||||||
value: gasLimit,
|
|
||||||
onChange: (newLimit) => this.updateGasLimit(newLimit),
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
h('div.gas-tooltip-arrow', {}),
|
|
||||||
]),
|
|
||||||
])
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
// const Component = require('react').Component
|
|
||||||
// const h = require('react-hyperscript')
|
|
||||||
// const inherits = require('util').inherits
|
|
||||||
// const Identicon = require('../identicon')
|
|
||||||
|
|
||||||
// module.exports = MemoTextArea
|
|
||||||
|
|
||||||
// inherits(MemoTextArea, Component)
|
|
||||||
// function MemoTextArea () {
|
|
||||||
// Component.call(this)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// MemoTextArea.prototype.render = function () {
|
|
||||||
// const { memo, identities, onChange } = this.props
|
|
||||||
|
|
||||||
// return h('div.send-v2__memo-text-area', [
|
|
||||||
|
|
||||||
// h('textarea.send-v2__memo-text-area__input', {
|
|
||||||
// placeholder: 'Optional',
|
|
||||||
// value: memo,
|
|
||||||
// onChange,
|
|
||||||
// // onBlur: () => {
|
|
||||||
// // this.setErrorsFor('memo')
|
|
||||||
// // },
|
|
||||||
// onFocus: event => {
|
|
||||||
// // this.clearErrorsFor('memo')
|
|
||||||
// },
|
|
||||||
// }),
|
|
||||||
|
|
||||||
// ])
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
|||||||
const {
|
|
||||||
addCurrencies,
|
|
||||||
conversionUtil,
|
|
||||||
conversionGTE,
|
|
||||||
multiplyCurrencies,
|
|
||||||
} = require('../../conversion-util')
|
|
||||||
const {
|
|
||||||
calcTokenAmount,
|
|
||||||
} = require('../../token-util')
|
|
||||||
|
|
||||||
function isBalanceSufficient ({
|
|
||||||
amount = '0x0',
|
|
||||||
gasTotal = '0x0',
|
|
||||||
balance,
|
|
||||||
primaryCurrency,
|
|
||||||
amountConversionRate,
|
|
||||||
conversionRate,
|
|
||||||
}) {
|
|
||||||
const totalAmount = addCurrencies(amount, gasTotal, {
|
|
||||||
aBase: 16,
|
|
||||||
bBase: 16,
|
|
||||||
toNumericBase: 'hex',
|
|
||||||
})
|
|
||||||
|
|
||||||
const balanceIsSufficient = conversionGTE(
|
|
||||||
{
|
|
||||||
value: balance,
|
|
||||||
fromNumericBase: 'hex',
|
|
||||||
fromCurrency: primaryCurrency,
|
|
||||||
conversionRate,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: totalAmount,
|
|
||||||
fromNumericBase: 'hex',
|
|
||||||
conversionRate: amountConversionRate || conversionRate,
|
|
||||||
fromCurrency: primaryCurrency,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return balanceIsSufficient
|
|
||||||
}
|
|
||||||
|
|
||||||
function isTokenBalanceSufficient ({
|
|
||||||
amount = '0x0',
|
|
||||||
tokenBalance,
|
|
||||||
decimals,
|
|
||||||
}) {
|
|
||||||
const amountInDec = conversionUtil(amount, {
|
|
||||||
fromNumericBase: 'hex',
|
|
||||||
})
|
|
||||||
|
|
||||||
const tokenBalanceIsSufficient = conversionGTE(
|
|
||||||
{
|
|
||||||
value: tokenBalance,
|
|
||||||
fromNumericBase: 'dec',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: calcTokenAmount(amountInDec, decimals),
|
|
||||||
fromNumericBase: 'dec',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return tokenBalanceIsSufficient
|
|
||||||
}
|
|
||||||
|
|
||||||
function getGasTotal (gasLimit, gasPrice) {
|
|
||||||
return multiplyCurrencies(gasLimit, gasPrice, {
|
|
||||||
toNumericBase: 'hex',
|
|
||||||
multiplicandBase: 16,
|
|
||||||
multiplierBase: 16,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
getGasTotal,
|
|
||||||
isBalanceSufficient,
|
|
||||||
isTokenBalanceSufficient,
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
const connect = require('react-redux').connect
|
|
||||||
const actions = require('../../actions')
|
|
||||||
const abi = require('ethereumjs-abi')
|
|
||||||
const SendEther = require('../../send-v2')
|
|
||||||
const { withRouter } = require('react-router-dom')
|
|
||||||
const { compose } = require('recompose')
|
|
||||||
|
|
||||||
const {
|
|
||||||
accountsWithSendEtherInfoSelector,
|
|
||||||
getCurrentAccountWithSendEtherInfo,
|
|
||||||
conversionRateSelector,
|
|
||||||
getSelectedToken,
|
|
||||||
getSelectedAddress,
|
|
||||||
getAddressBook,
|
|
||||||
getSendFrom,
|
|
||||||
getCurrentCurrency,
|
|
||||||
getSelectedTokenToFiatRate,
|
|
||||||
getSelectedTokenContract,
|
|
||||||
} = require('../../selectors')
|
|
||||||
|
|
||||||
module.exports = compose(
|
|
||||||
withRouter,
|
|
||||||
connect(mapStateToProps, mapDispatchToProps)
|
|
||||||
)(SendEther)
|
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
|
||||||
const fromAccounts = accountsWithSendEtherInfoSelector(state)
|
|
||||||
const selectedAddress = getSelectedAddress(state)
|
|
||||||
const selectedToken = getSelectedToken(state)
|
|
||||||
const conversionRate = conversionRateSelector(state)
|
|
||||||
|
|
||||||
let data
|
|
||||||
let primaryCurrency
|
|
||||||
let tokenToFiatRate
|
|
||||||
if (selectedToken) {
|
|
||||||
data = Array.prototype.map.call(
|
|
||||||
abi.rawEncode(['address', 'uint256'], [selectedAddress, '0x0']),
|
|
||||||
x => ('00' + x.toString(16)).slice(-2)
|
|
||||||
).join('')
|
|
||||||
|
|
||||||
primaryCurrency = selectedToken.symbol
|
|
||||||
|
|
||||||
tokenToFiatRate = getSelectedTokenToFiatRate(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state.metamask.send,
|
|
||||||
from: getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state),
|
|
||||||
fromAccounts,
|
|
||||||
toAccounts: [...fromAccounts, ...getAddressBook(state)],
|
|
||||||
conversionRate,
|
|
||||||
selectedToken,
|
|
||||||
primaryCurrency,
|
|
||||||
convertedCurrency: getCurrentCurrency(state),
|
|
||||||
data,
|
|
||||||
selectedAddress,
|
|
||||||
amountConversionRate: selectedToken ? tokenToFiatRate : conversionRate,
|
|
||||||
tokenContract: getSelectedTokenContract(state),
|
|
||||||
unapprovedTxs: state.metamask.unapprovedTxs,
|
|
||||||
network: state.metamask.network,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
|
||||||
return {
|
|
||||||
showCustomizeGasModal: () => dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })),
|
|
||||||
estimateGas: params => dispatch(actions.estimateGas(params)),
|
|
||||||
getGasPrice: () => dispatch(actions.getGasPrice()),
|
|
||||||
signTokenTx: (tokenAddress, toAddress, amount, txData) => (
|
|
||||||
dispatch(actions.signTokenTx(tokenAddress, toAddress, amount, txData))
|
|
||||||
),
|
|
||||||
signTx: txParams => dispatch(actions.signTx(txParams)),
|
|
||||||
updateAndApproveTx: txParams => dispatch(actions.updateAndApproveTx(txParams)),
|
|
||||||
updateTx: txData => dispatch(actions.updateTransaction(txData)),
|
|
||||||
setSelectedAddress: address => dispatch(actions.setSelectedAddress(address)),
|
|
||||||
addToAddressBook: (address, nickname) => dispatch(actions.addToAddressBook(address, nickname)),
|
|
||||||
updateGasTotal: newTotal => dispatch(actions.updateGasTotal(newTotal)),
|
|
||||||
updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)),
|
|
||||||
updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)),
|
|
||||||
updateSendTokenBalance: tokenBalance => dispatch(actions.updateSendTokenBalance(tokenBalance)),
|
|
||||||
updateSendFrom: newFrom => dispatch(actions.updateSendFrom(newFrom)),
|
|
||||||
updateSendTo: (newTo, nickname) => dispatch(actions.updateSendTo(newTo, nickname)),
|
|
||||||
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
|
|
||||||
updateSendMemo: newMemo => dispatch(actions.updateSendMemo(newMemo)),
|
|
||||||
updateSendErrors: newError => dispatch(actions.updateSendErrors(newError)),
|
|
||||||
clearSend: () => dispatch(actions.clearSend()),
|
|
||||||
setMaxModeTo: bool => dispatch(actions.setMaxModeTo(bool)),
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,7 +2,7 @@ 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 inherits = require('util').inherits
|
const inherits = require('util').inherits
|
||||||
const AccountListItem = require('./account-list-item')
|
const AccountListItem = require('../send_/account-list-item/account-list-item.component').default
|
||||||
const connect = require('react-redux').connect
|
const connect = require('react-redux').connect
|
||||||
|
|
||||||
ToAutoComplete.contextTypes = {
|
ToAutoComplete.contextTypes = {
|
||||||
|
0
ui/app/components/send_/README.md
Normal file
0
ui/app/components/send_/README.md
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { checksumAddress } from '../../../util'
|
||||||
|
import Identicon from '../../identicon'
|
||||||
|
import CurrencyDisplay from '../../send/currency-display'
|
||||||
|
|
||||||
|
export default class AccountListItem extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
account: PropTypes.object,
|
||||||
|
className: PropTypes.string,
|
||||||
|
conversionRate: PropTypes.number,
|
||||||
|
currentCurrency: PropTypes.string,
|
||||||
|
displayAddress: PropTypes.bool,
|
||||||
|
displayBalance: PropTypes.bool,
|
||||||
|
handleClick: PropTypes.func,
|
||||||
|
icon: PropTypes.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
account,
|
||||||
|
className,
|
||||||
|
conversionRate,
|
||||||
|
currentCurrency,
|
||||||
|
displayAddress = false,
|
||||||
|
displayBalance = true,
|
||||||
|
handleClick,
|
||||||
|
icon = null,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const { name, address, balance } = account || {}
|
||||||
|
|
||||||
|
return (<div
|
||||||
|
className={`account-list-item ${className}`}
|
||||||
|
onClick={() => handleClick({ name, address, balance })}
|
||||||
|
>
|
||||||
|
|
||||||
|
<div className="account-list-item__top-row">
|
||||||
|
<Identicon
|
||||||
|
address={address}
|
||||||
|
className="account-list-item__identicon"
|
||||||
|
diameter={18}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="account-list-item__account-name">{ name || address }</div>
|
||||||
|
|
||||||
|
{icon && <div className="account-list-item__icon">{ icon }</div>}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{displayAddress && name && <div className="account-list-item__account-address">
|
||||||
|
{ checksumAddress(address) }
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
{displayBalance && <CurrencyDisplay
|
||||||
|
className="account-list-item__account-balances"
|
||||||
|
conversionRate={conversionRate}
|
||||||
|
convertedBalanceClassName="account-list-item__account-secondary-balance"
|
||||||
|
convertedCurrency={currentCurrency}
|
||||||
|
primaryBalanceClassName="account-list-item__account-primary-balance"
|
||||||
|
primaryCurrency="ETH"
|
||||||
|
readOnly={true}
|
||||||
|
value={balance}
|
||||||
|
/>}
|
||||||
|
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountListItem.contextTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
|||||||
|
import { connect } from 'react-redux'
|
||||||
|
import {
|
||||||
|
getConversionRate,
|
||||||
|
getConvertedCurrency,
|
||||||
|
} from '../send.selectors.js'
|
||||||
|
import AccountListItem from './account-list-item.component'
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(AccountListItem)
|
||||||
|
|
||||||
|
function mapStateToProps (state) {
|
||||||
|
return {
|
||||||
|
conversionRate: getConversionRate(state),
|
||||||
|
currentCurrency: getConvertedCurrency(state),
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user