mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
fix merge conflicts
This commit is contained in:
commit
ab7eb73ecc
2
.babelrc
2
.babelrc
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"presets": [["env", { "debug": true }], "react", "stage-0"],
|
"presets": [["env"], "react", "stage-0"],
|
||||||
"plugins": ["transform-runtime", "transform-async-to-generator", "transform-class-properties"]
|
"plugins": ["transform-runtime", "transform-async-to-generator", "transform-class-properties"]
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ workflows:
|
|||||||
full_test:
|
full_test:
|
||||||
jobs:
|
jobs:
|
||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
- prep-deps-firefox
|
|
||||||
- prep-build:
|
- prep-build:
|
||||||
requires:
|
requires:
|
||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
@ -28,7 +27,6 @@ workflows:
|
|||||||
- test-e2e-firefox:
|
- test-e2e-firefox:
|
||||||
requires:
|
requires:
|
||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
- prep-deps-firefox
|
|
||||||
- prep-build
|
- prep-build
|
||||||
- test-e2e-beta-chrome:
|
- test-e2e-beta-chrome:
|
||||||
requires:
|
requires:
|
||||||
@ -37,7 +35,6 @@ workflows:
|
|||||||
- test-e2e-beta-firefox:
|
- test-e2e-beta-firefox:
|
||||||
requires:
|
requires:
|
||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
- prep-deps-firefox
|
|
||||||
- prep-build
|
- prep-build
|
||||||
- test-unit:
|
- test-unit:
|
||||||
requires:
|
requires:
|
||||||
@ -49,7 +46,6 @@ workflows:
|
|||||||
- test-integration-mascara-firefox:
|
- test-integration-mascara-firefox:
|
||||||
requires:
|
requires:
|
||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
- prep-deps-firefox
|
|
||||||
- prep-scss
|
- prep-scss
|
||||||
- test-integration-flat-chrome:
|
- test-integration-flat-chrome:
|
||||||
requires:
|
requires:
|
||||||
@ -58,7 +54,6 @@ workflows:
|
|||||||
- test-integration-flat-firefox:
|
- test-integration-flat-firefox:
|
||||||
requires:
|
requires:
|
||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
- prep-deps-firefox
|
|
||||||
- prep-scss
|
- prep-scss
|
||||||
- all-tests-pass:
|
- all-tests-pass:
|
||||||
requires:
|
requires:
|
||||||
@ -103,46 +98,34 @@ jobs:
|
|||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
- v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
||||||
# fallback to using the latest cache if no exact match is found
|
|
||||||
- v1.0-dependency-cache-
|
|
||||||
- run:
|
- run:
|
||||||
name: Install npm 6 + deps via npm
|
name: Install npm 6 + deps via npm
|
||||||
command: |
|
command: |
|
||||||
sudo npm install -g npm@6.1.0 && npm install --no-save
|
sudo npm install -g npm@6 && npm install --no-save
|
||||||
|
- persist_to_workspace:
|
||||||
|
root: .
|
||||||
|
paths:
|
||||||
|
- node_modules
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
||||||
paths:
|
paths:
|
||||||
- node_modules
|
- node_modules
|
||||||
prep-deps-firefox:
|
|
||||||
docker:
|
|
||||||
- image: circleci/node:8.11.3-browsers
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- restore_cache:
|
|
||||||
key: v1.0-dependency-cache-firefox-
|
|
||||||
- run:
|
|
||||||
name: Download Firefox If needed
|
|
||||||
command: ./.circleci/scripts/firefox-download.sh
|
|
||||||
- save_cache:
|
|
||||||
key: v1.0-dependency-cache-firefox-
|
|
||||||
paths:
|
|
||||||
- firefox
|
|
||||||
|
|
||||||
prep-build:
|
prep-build:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:8.11.3-browsers
|
- image: circleci/node:8.11.3-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- attach_workspace:
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
at: .
|
||||||
- run:
|
- run:
|
||||||
name: build:dist
|
name: build:dist
|
||||||
command: npm run dist
|
command: npm run dist
|
||||||
- run:
|
- run:
|
||||||
name: build:debug
|
name: build:debug
|
||||||
command: find dist/ -type f -exec md5sum {} \; | sort -k 2
|
command: find dist/ -type f -exec md5sum {} \; | sort -k 2
|
||||||
- save_cache:
|
- persist_to_workspace:
|
||||||
key: build-cache-{{ .Revision }}
|
root: .
|
||||||
paths:
|
paths:
|
||||||
- dist
|
- dist
|
||||||
- builds
|
- builds
|
||||||
@ -152,23 +135,23 @@ jobs:
|
|||||||
- image: circleci/node:8.11.3-browsers
|
- image: circleci/node:8.11.3-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- attach_workspace:
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
at: .
|
||||||
- run:
|
- run:
|
||||||
name: build:dist
|
name: build:dist
|
||||||
command: npm run doc
|
command: npm run doc
|
||||||
- save_cache:
|
- persist_to_workspace:
|
||||||
key: docs-cache-{{ .Revision }}
|
root: .
|
||||||
paths:
|
paths:
|
||||||
- docs/jsdoc
|
- docs/jsdocs
|
||||||
|
|
||||||
prep-scss:
|
prep-scss:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:8.11.3-browsers
|
- image: circleci/node:8.11.3-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- attach_workspace:
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
at: .
|
||||||
- run:
|
- run:
|
||||||
name: Get Scss Cache key
|
name: Get Scss Cache key
|
||||||
# this allows us to checksum against a whole directory
|
# this allows us to checksum against a whole directory
|
||||||
@ -176,8 +159,8 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Build for integration tests
|
name: Build for integration tests
|
||||||
command: npm run test:integration:build
|
command: npm run test:integration:build
|
||||||
- save_cache:
|
- persist_to_workspace:
|
||||||
key: scss-cache-{{ checksum "scss_checksum" }}
|
root: .
|
||||||
paths:
|
paths:
|
||||||
- ui/app/css/output
|
- ui/app/css/output
|
||||||
|
|
||||||
@ -186,8 +169,8 @@ jobs:
|
|||||||
- image: circleci/node:8.11.3-browsers
|
- image: circleci/node:8.11.3-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- attach_workspace:
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
at: .
|
||||||
- run:
|
- run:
|
||||||
name: Test
|
name: Test
|
||||||
command: npm run lint
|
command: npm run lint
|
||||||
@ -197,8 +180,8 @@ jobs:
|
|||||||
- image: circleci/node:8.11.3-browsers
|
- image: circleci/node:8.11.3-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- attach_workspace:
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
at: .
|
||||||
- run:
|
- run:
|
||||||
name: Test
|
name: Test
|
||||||
command: npx nsp check
|
command: npx nsp check
|
||||||
@ -208,10 +191,8 @@ jobs:
|
|||||||
- image: circleci/node:8.11.3-browsers
|
- image: circleci/node:8.11.3-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- attach_workspace:
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
at: .
|
||||||
- restore_cache:
|
|
||||||
key: build-cache-{{ .Revision }}
|
|
||||||
- run:
|
- run:
|
||||||
name: test:e2e:chrome
|
name: test:e2e:chrome
|
||||||
command: npm run test:e2e:chrome
|
command: npm run test:e2e:chrome
|
||||||
@ -224,15 +205,11 @@ jobs:
|
|||||||
- image: circleci/node:8.11.3-browsers
|
- image: circleci/node:8.11.3-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
|
||||||
key: v1.0-dependency-cache-firefox-
|
|
||||||
- run:
|
- run:
|
||||||
name: Install firefox
|
name: Install Firefox
|
||||||
command: ./.circleci/scripts/firefox-install.sh
|
command: ./.circleci/scripts/firefox-install
|
||||||
- restore_cache:
|
- attach_workspace:
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
at: .
|
||||||
- restore_cache:
|
|
||||||
key: build-cache-{{ .Revision }}
|
|
||||||
- run:
|
- run:
|
||||||
name: test:e2e:firefox
|
name: test:e2e:firefox
|
||||||
command: npm run test:e2e:firefox
|
command: npm run test:e2e:firefox
|
||||||
@ -245,10 +222,8 @@ jobs:
|
|||||||
- image: circleci/node:8.11.3-browsers
|
- image: circleci/node:8.11.3-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- attach_workspace:
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
at: .
|
||||||
- restore_cache:
|
|
||||||
key: build-cache-{{ .Revision }}
|
|
||||||
- run:
|
- run:
|
||||||
name: test:e2e:chrome:beta
|
name: test:e2e:chrome:beta
|
||||||
command: npm run test:e2e:chrome:beta
|
command: npm run test:e2e:chrome:beta
|
||||||
@ -261,15 +236,11 @@ jobs:
|
|||||||
- image: circleci/node:8.11.3-browsers
|
- image: circleci/node:8.11.3-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
|
||||||
key: v1.0-dependency-cache-firefox-
|
|
||||||
- run:
|
- run:
|
||||||
name: Install firefox
|
name: Install Firefox
|
||||||
command: ./.circleci/scripts/firefox-install.sh
|
command: ./.circleci/scripts/firefox-install
|
||||||
- restore_cache:
|
- attach_workspace:
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
at: .
|
||||||
- restore_cache:
|
|
||||||
key: build-cache-{{ .Revision }}
|
|
||||||
- run:
|
- run:
|
||||||
name: test:e2e:firefox:beta
|
name: test:e2e:firefox:beta
|
||||||
command: npm run test:e2e:firefox:beta
|
command: npm run test:e2e:firefox:beta
|
||||||
@ -282,15 +253,13 @@ jobs:
|
|||||||
- image: circleci/node:8.11.3-browsers
|
- image: circleci/node:8.11.3-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- attach_workspace:
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
at: .
|
||||||
- restore_cache:
|
|
||||||
key: build-cache-{{ .Revision }}
|
|
||||||
- run:
|
- run:
|
||||||
name: Test
|
name: Test
|
||||||
command: npm run test:screens
|
command: npm run test:screens
|
||||||
- save_cache:
|
- persist_to_workspace:
|
||||||
key: job-screens-{{ .Revision }}
|
root: .
|
||||||
paths:
|
paths:
|
||||||
- test-artifacts
|
- test-artifacts
|
||||||
|
|
||||||
@ -299,12 +268,8 @@ jobs:
|
|||||||
- image: circleci/node:8.11.3-browsers
|
- image: circleci/node:8.11.3-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- attach_workspace:
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
at: .
|
||||||
- restore_cache:
|
|
||||||
key: build-cache-{{ .Revision }}
|
|
||||||
- restore_cache:
|
|
||||||
key: job-screens-{{ .Revision }}
|
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: dist/mascara
|
path: dist/mascara
|
||||||
destination: builds/mascara
|
destination: builds/mascara
|
||||||
@ -326,14 +291,8 @@ jobs:
|
|||||||
- image: circleci/node:8.11.3-browsers
|
- image: circleci/node:8.11.3-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- attach_workspace:
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
at: .
|
||||||
- restore_cache:
|
|
||||||
key: build-cache-{{ .Revision }}
|
|
||||||
- restore_cache:
|
|
||||||
key: docs-cache-{{ .Revision }}
|
|
||||||
- restore_cache:
|
|
||||||
key: job-screens-{{ .Revision }}
|
|
||||||
- run:
|
- run:
|
||||||
name: sentry sourcemaps upload
|
name: sentry sourcemaps upload
|
||||||
command: npm run sentry:publish
|
command: npm run sentry:publish
|
||||||
@ -349,32 +308,22 @@ jobs:
|
|||||||
- image: circleci/node:8.11.3-browsers
|
- image: circleci/node:8.11.3-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- attach_workspace:
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
at: .
|
||||||
- run:
|
- run:
|
||||||
name: test:coverage
|
name: test:coverage
|
||||||
command: npm run test:coverage
|
command: npm run test:coverage
|
||||||
|
|
||||||
test-integration-flat-firefox:
|
test-integration-flat-firefox:
|
||||||
environment:
|
|
||||||
browsers: '["Firefox"]'
|
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:8.11.3-browsers
|
- image: circleci/node:8.11.3-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- attach_workspace:
|
||||||
key: v1.0-dependency-cache-firefox-
|
at: .
|
||||||
- run:
|
- run:
|
||||||
name: Install firefox
|
name: Install Firefox
|
||||||
command: ./.circleci/scripts/firefox-install.sh
|
command: ./.circleci/scripts/firefox-install
|
||||||
- restore_cache:
|
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
|
||||||
- run:
|
|
||||||
name: Get Scss Cache key
|
|
||||||
# this allows us to checksum against a whole directory
|
|
||||||
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum
|
|
||||||
- restore_cache:
|
|
||||||
key: scss-cache-{{ checksum "scss_checksum" }}
|
|
||||||
- run:
|
- run:
|
||||||
name: test:integration:flat
|
name: test:integration:flat
|
||||||
command: npm run test:flat
|
command: npm run test:flat
|
||||||
@ -386,38 +335,22 @@ jobs:
|
|||||||
- image: circleci/node:8.11.3-browsers
|
- image: circleci/node:8.11.3-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- attach_workspace:
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
at: .
|
||||||
- run:
|
|
||||||
name: Get Scss Cache key
|
|
||||||
# this allows us to checksum against a whole directory
|
|
||||||
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum
|
|
||||||
- restore_cache:
|
|
||||||
key: scss-cache-{{ checksum "scss_checksum" }}
|
|
||||||
- run:
|
- run:
|
||||||
name: test:integration:flat
|
name: test:integration:flat
|
||||||
command: npm run test:flat
|
command: npm run test:flat
|
||||||
|
|
||||||
test-integration-mascara-firefox:
|
test-integration-mascara-firefox:
|
||||||
environment:
|
|
||||||
browsers: '["Firefox"]'
|
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:8.11.3-browsers
|
- image: circleci/node:8.11.3-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- attach_workspace:
|
||||||
key: v1.0-dependency-cache-firefox-
|
at: .
|
||||||
- run:
|
- run:
|
||||||
name: Install firefox
|
name: Install Firefox
|
||||||
command: ./.circleci/scripts/firefox-install.sh
|
command: ./.circleci/scripts/firefox-install
|
||||||
- restore_cache:
|
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
|
||||||
- run:
|
|
||||||
name: Get Scss Cache key
|
|
||||||
# this allows us to checksum against a whole directory
|
|
||||||
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum
|
|
||||||
- restore_cache:
|
|
||||||
key: scss-cache-{{ checksum "scss_checksum" }}
|
|
||||||
- run:
|
- run:
|
||||||
name: test:integration:mascara
|
name: test:integration:mascara
|
||||||
command: npm run test:mascara
|
command: npm run test:mascara
|
||||||
@ -429,14 +362,8 @@ jobs:
|
|||||||
- image: circleci/node:8.11.3-browsers
|
- image: circleci/node:8.11.3-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- attach_workspace:
|
||||||
key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
|
at: .
|
||||||
- run:
|
|
||||||
name: Get Scss Cache key
|
|
||||||
# this allows us to checksum against a whole directory
|
|
||||||
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum
|
|
||||||
- restore_cache:
|
|
||||||
key: scss-cache-{{ checksum "scss_checksum" }}
|
|
||||||
- run:
|
- run:
|
||||||
name: test:integration:mascara
|
name: test:integration:mascara
|
||||||
command: npm run test:mascara
|
command: npm run test:mascara
|
||||||
@ -447,4 +374,4 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- run:
|
- run:
|
||||||
name: All Tests Passed
|
name: All Tests Passed
|
||||||
command: echo 'weew - everything passed!'
|
command: echo 'weew - everything passed!'
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
echo "Checking if firefox was already downloaded"
|
|
||||||
if [ -d "firefox" ]
|
|
||||||
then
|
|
||||||
echo "Firefox found. No need to download"
|
|
||||||
else
|
|
||||||
FIREFOX_VERSION="61.0.1"
|
|
||||||
FIREFOX_BINARY="firefox-$FIREFOX_VERSION.tar.bz2"
|
|
||||||
echo "Downloading firefox..."
|
|
||||||
wget "https://ftp.mozilla.org/pub/firefox/releases/$FIREFOX_VERSION/linux-x86_64/en-US/$FIREFOX_BINARY" \
|
|
||||||
&& tar xjf "$FIREFOX_BINARY"
|
|
||||||
echo "firefox download complete"
|
|
||||||
fi
|
|
29
.circleci/scripts/firefox-install
Executable file
29
.circleci/scripts/firefox-install
Executable file
@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
FIREFOX_VERSION='61.0.2'
|
||||||
|
FIREFOX_BINARY="firefox-${FIREFOX_VERSION}.tar.bz2"
|
||||||
|
FIREFOX_BINARY_URL="https://ftp.mozilla.org/pub/firefox/releases/${FIREFOX_VERSION}/linux-x86_64/en-US/${FIREFOX_BINARY}"
|
||||||
|
FIREFOX_PATH='/opt/firefox'
|
||||||
|
|
||||||
|
printf '%s\n' "Removing old Firefox installation"
|
||||||
|
|
||||||
|
sudo rm -r "${FIREFOX_PATH}"
|
||||||
|
|
||||||
|
printf '%s\n' "Downloading & installing Firefox ${FIREFOX_VERSION}"
|
||||||
|
|
||||||
|
wget --quiet --show-progress -O- "${FIREFOX_BINARY_URL}" | sudo tar xj -C /opt
|
||||||
|
|
||||||
|
printf '%s\n' "Firefox ${FIREFOX_VERSION} installed"
|
||||||
|
|
||||||
|
{
|
||||||
|
printf '%s\n' 'pref("general.config.filename", "firefox.cfg");'
|
||||||
|
printf '%s\n' 'pref("general.config.obscure_value", 0);'
|
||||||
|
} | sudo tee "${FIREFOX_PATH}/defaults/pref/autoconfig.js"
|
||||||
|
|
||||||
|
sudo cp .circleci/scripts/firefox.cfg "${FIREFOX_PATH}"
|
||||||
|
|
||||||
|
printf '%s\n' "Firefox ${FIREFOX_VERSION} configured"
|
@ -1,8 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
echo "Installing firefox..."
|
|
||||||
sudo rm -r /opt/firefox
|
|
||||||
sudo mv firefox /opt/firefox61
|
|
||||||
sudo mv /usr/bin/firefox /usr/bin/firefox-old
|
|
||||||
sudo ln -s /opt/firefox61/firefox /usr/bin/firefox
|
|
||||||
echo "Firefox installed."
|
|
13
.circleci/scripts/firefox.cfg
Normal file
13
.circleci/scripts/firefox.cfg
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// IMPORTANT: Start your code on the 2nd line
|
||||||
|
|
||||||
|
lockPref("app.update.enabled", false);
|
||||||
|
lockPref("app.update.auto", false);
|
||||||
|
lockPref("app.update.mode", 0);
|
||||||
|
lockPref("app.update.service.enabled", false);
|
||||||
|
|
||||||
|
pref("browser.rights.3.shown", true);
|
||||||
|
|
||||||
|
pref("browser.startup.homepage_override.mstone","ignore");
|
||||||
|
|
||||||
|
lockPref("plugins.hide_infobar_for_outdated_plugin", true);
|
||||||
|
clearPref("plugins.update.url");
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -9,6 +9,7 @@ package
|
|||||||
# IDEs
|
# IDEs
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
.sublime-project
|
||||||
|
|
||||||
# VIM
|
# VIM
|
||||||
*.swp
|
*.swp
|
||||||
@ -34,6 +35,7 @@ test/bundle.js
|
|||||||
test/test-bundle.js
|
test/test-bundle.js
|
||||||
|
|
||||||
test-artifacts
|
test-artifacts
|
||||||
|
test-builds
|
||||||
|
|
||||||
#ignore css output and sourcemaps
|
#ignore css output and sourcemaps
|
||||||
ui/app/css/output/
|
ui/app/css/output/
|
||||||
|
12
CHANGELOG.md
12
CHANGELOG.md
@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
## Current Develop Branch
|
## Current Develop Branch
|
||||||
|
|
||||||
|
## 4.9.3 Wed Aug 15 2018
|
||||||
|
|
||||||
|
- (#4897)[https://github.com/MetaMask/metamask-extension/pull/4897]: QR code scan for recipient addresses.
|
||||||
|
- (#4961)[https://github.com/MetaMask/metamask-extension/pull/4961]: Add a download seed phrase link.
|
||||||
|
- (#5060)[https://github.com/MetaMask/metamask-extension/pull/5060]: Fix bug where gas was not updating properly.
|
||||||
|
|
||||||
|
## 4.9.2 Mon Aug 09 2018
|
||||||
|
|
||||||
|
- [#5020](https://github.com/MetaMask/metamask-extension/pull/5020): Fix bug in migration #28 ( moving tokens to specific accounts )
|
||||||
|
|
||||||
## 4.9.1 Mon Aug 09 2018
|
## 4.9.1 Mon Aug 09 2018
|
||||||
|
|
||||||
- [#4884](https://github.com/MetaMask/metamask-extension/pull/4884): Allow to have tokens per account and network.
|
- [#4884](https://github.com/MetaMask/metamask-extension/pull/4884): Allow to have tokens per account and network.
|
||||||
@ -23,6 +33,8 @@
|
|||||||
- [#4691](https://github.com/MetaMask/metamask-extension/pull/4691): Redesign of the Confirm Transaction Screen.
|
- [#4691](https://github.com/MetaMask/metamask-extension/pull/4691): Redesign of the Confirm Transaction Screen.
|
||||||
- [#4840](https://github.com/MetaMask/metamask-extension/pull/4840): Now shows notifications when transactions are completed.
|
- [#4840](https://github.com/MetaMask/metamask-extension/pull/4840): Now shows notifications when transactions are completed.
|
||||||
- [#4855](https://github.com/MetaMask/metamask-extension/pull/4855): Allow the use of HTTP prefix for custom rpc urls.
|
- [#4855](https://github.com/MetaMask/metamask-extension/pull/4855): Allow the use of HTTP prefix for custom rpc urls.
|
||||||
|
- [#4855](https://github.com/MetaMask/metamask-extension/pull/4855): network.js: convert rpc protocol to lower case.
|
||||||
|
- [#4898](https://github.com/MetaMask/metamask-extension/pull/4898): Restore multiple consecutive accounts with balances.
|
||||||
|
|
||||||
## 4.8.0 Thur Jun 14 2018
|
## 4.8.0 Thur Jun 14 2018
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@
|
|||||||
"message": "Auf Coinbase kaufen"
|
"message": "Auf Coinbase kaufen"
|
||||||
},
|
},
|
||||||
"buyCoinbaseExplainer": {
|
"buyCoinbaseExplainer": {
|
||||||
"message": "Coinbase ist die weltweit bekannteste Art und Weise um bitcoin, ethereum und litecoin zu kaufen und verkaufen."
|
"message": "Coinbase ist die weltweit bekannteste Art und Weise um Bitcoin, Ethereum und Litecoin zu kaufen und verkaufen."
|
||||||
},
|
},
|
||||||
"ok": {
|
"ok": {
|
||||||
"message": "Ok"
|
"message": "Ok"
|
||||||
@ -828,7 +828,7 @@
|
|||||||
"message": "Willkommen zur neuen Oberfläche (Beta)"
|
"message": "Willkommen zur neuen Oberfläche (Beta)"
|
||||||
},
|
},
|
||||||
"uiWelcomeMessage": {
|
"uiWelcomeMessage": {
|
||||||
"message": "Du verwendest nun die neue Metamask Oberfläche. Schau dich um, teste die neuen Features wie z.B. das Senden von Token und lass es uns wissen falls du irgendwelche Probleme hast."
|
"message": "Du verwendest nun die neue MetaMask Oberfläche. Schau dich um, teste die neuen Features wie z.B. das Senden von Token und lass es uns wissen falls du irgendwelche Probleme hast."
|
||||||
},
|
},
|
||||||
"unapproved": {
|
"unapproved": {
|
||||||
"message": "Nicht genehmigt"
|
"message": "Nicht genehmigt"
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
"accept": {
|
"accept": {
|
||||||
"message": "Accept"
|
"message": "Accept"
|
||||||
},
|
},
|
||||||
|
"accessingYourCamera": {
|
||||||
|
"message": "Accesing your camera..."
|
||||||
|
},
|
||||||
"account": {
|
"account": {
|
||||||
"message": "Account"
|
"message": "Account"
|
||||||
},
|
},
|
||||||
@ -96,7 +99,7 @@
|
|||||||
"message": "Buy on Coinbase"
|
"message": "Buy on Coinbase"
|
||||||
},
|
},
|
||||||
"buyCoinbaseExplainer": {
|
"buyCoinbaseExplainer": {
|
||||||
"message": "Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin."
|
"message": "Coinbase is the world’s most popular way to buy and sell Bitcoin, Ethereum, and Litecoin."
|
||||||
},
|
},
|
||||||
"bytes": {
|
"bytes": {
|
||||||
"message": "Bytes"
|
"message": "Bytes"
|
||||||
@ -116,8 +119,8 @@
|
|||||||
"close": {
|
"close": {
|
||||||
"message": "Close"
|
"message": "Close"
|
||||||
},
|
},
|
||||||
"chromeRequiredForTrezor":{
|
"chromeRequiredForHardwareWallets":{
|
||||||
"message": "You need to use Metamask on Google Chrome in order to connect to your TREZOR device."
|
"message": "You need to use MetaMask on Google Chrome in order to connect to your Hardware Wallet."
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"message": "Confirm"
|
"message": "Confirm"
|
||||||
@ -143,15 +146,12 @@
|
|||||||
"connecting": {
|
"connecting": {
|
||||||
"message": "Connecting..."
|
"message": "Connecting..."
|
||||||
},
|
},
|
||||||
|
"connectToLedger": {
|
||||||
|
"message": "Connect to Ledger"
|
||||||
|
},
|
||||||
"connectToTrezor": {
|
"connectToTrezor": {
|
||||||
"message": "Connect to Trezor"
|
"message": "Connect to Trezor"
|
||||||
},
|
},
|
||||||
"connectToTrezorHelp": {
|
|
||||||
"message": "Metamask is able to access your TREZOR ethereum accounts. First make sure your device is connected and unlocked."
|
|
||||||
},
|
|
||||||
"connectToTrezorTrouble": {
|
|
||||||
"message": "If you are having trouble, please make sure you are using the latest version of the TREZOR firmware."
|
|
||||||
},
|
|
||||||
"continue": {
|
"continue": {
|
||||||
"message": "Continue"
|
"message": "Continue"
|
||||||
},
|
},
|
||||||
@ -286,8 +286,8 @@
|
|||||||
"downloadStateLogs": {
|
"downloadStateLogs": {
|
||||||
"message": "Download State Logs"
|
"message": "Download State Logs"
|
||||||
},
|
},
|
||||||
"dontHaveATrezorWallet": {
|
"dontHaveAHardwareWallet": {
|
||||||
"message": "Don't have a TREZOR hardware wallet?"
|
"message": "Don’t have a hardware wallet?"
|
||||||
},
|
},
|
||||||
"dropped": {
|
"dropped": {
|
||||||
"message": "Dropped"
|
"message": "Dropped"
|
||||||
@ -423,11 +423,11 @@
|
|||||||
"hardwareWalletConnected": {
|
"hardwareWalletConnected": {
|
||||||
"message": "Hardware wallet connected"
|
"message": "Hardware wallet connected"
|
||||||
},
|
},
|
||||||
"hardwareSupport": {
|
"hardwareWallets": {
|
||||||
"message": "Hardware Support"
|
"message": "Connect a hardware wallet"
|
||||||
},
|
},
|
||||||
"hardwareSupportMsg": {
|
"hardwareWalletsMsg": {
|
||||||
"message": "You can now view your Hardware accounts in MetaMask! Scroll down and read how it works."
|
"message": "Select a hardware wallet you'd like to use with MetaMask"
|
||||||
},
|
},
|
||||||
"havingTroubleConnecting": {
|
"havingTroubleConnecting": {
|
||||||
"message": "Having trouble connecting?"
|
"message": "Having trouble connecting?"
|
||||||
@ -535,6 +535,9 @@
|
|||||||
"learnMore": {
|
"learnMore": {
|
||||||
"message": "Learn more"
|
"message": "Learn more"
|
||||||
},
|
},
|
||||||
|
"ledgerAccountRestriction": {
|
||||||
|
"message": "You need to make use your last account before you can add a new one."
|
||||||
|
},
|
||||||
"lessThanMax": {
|
"lessThanMax": {
|
||||||
"message": "must be less than or equal to $1.",
|
"message": "must be less than or equal to $1.",
|
||||||
"description": "helper for inputting hex as decimal input"
|
"description": "helper for inputting hex as decimal input"
|
||||||
@ -656,6 +659,12 @@
|
|||||||
"notStarted": {
|
"notStarted": {
|
||||||
"message": "Not Started"
|
"message": "Not Started"
|
||||||
},
|
},
|
||||||
|
"noWebcamFoundTitle": {
|
||||||
|
"message": "Webcam not found"
|
||||||
|
},
|
||||||
|
"noWebcamFound": {
|
||||||
|
"message": "Your computer's webcam was not found. Please try again."
|
||||||
|
},
|
||||||
"oldUI": {
|
"oldUI": {
|
||||||
"message": "Old UI"
|
"message": "Old UI"
|
||||||
},
|
},
|
||||||
@ -899,7 +908,7 @@
|
|||||||
"description": "displays token symbol"
|
"description": "displays token symbol"
|
||||||
},
|
},
|
||||||
"orderOneHere": {
|
"orderOneHere": {
|
||||||
"message": "Order one here."
|
"message": "Order a Trezor or Ledger and keep your funds in cold storage"
|
||||||
},
|
},
|
||||||
"searchTokens": {
|
"searchTokens": {
|
||||||
"message": "Search Tokens"
|
"message": "Search Tokens"
|
||||||
@ -911,7 +920,13 @@
|
|||||||
"message": "Select an Account"
|
"message": "Select an Account"
|
||||||
},
|
},
|
||||||
"selectAnAccountHelp": {
|
"selectAnAccountHelp": {
|
||||||
"message": "These are the accounts available in your hardware wallet. Select the one you’d like to use in MetaMask."
|
"message": "Select the account to view in MetaMask"
|
||||||
|
},
|
||||||
|
"selectHdPath": {
|
||||||
|
"message": "Select HD Path"
|
||||||
|
},
|
||||||
|
"selectPathHelp": {
|
||||||
|
"message": "If you don't see your existing Ledger accounts below, try switching paths to \"Legacy (MEW / MyCrypto)\""
|
||||||
},
|
},
|
||||||
"sendTokensAnywhere": {
|
"sendTokensAnywhere": {
|
||||||
"message": "Send Tokens to anyone with an Ethereum account"
|
"message": "Send Tokens to anyone with an Ethereum account"
|
||||||
@ -940,6 +955,12 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"message": "Info"
|
"message": "Info"
|
||||||
},
|
},
|
||||||
|
"scanInstructions": {
|
||||||
|
"message": "Place the QR code in front of your camera"
|
||||||
|
},
|
||||||
|
"scanQrCode": {
|
||||||
|
"message": "Scan QR Code"
|
||||||
|
},
|
||||||
"shapeshiftBuy": {
|
"shapeshiftBuy": {
|
||||||
"message": "Buy with Shapeshift"
|
"message": "Buy with Shapeshift"
|
||||||
},
|
},
|
||||||
@ -1059,6 +1080,9 @@
|
|||||||
"message": "We had trouble loading your token balances. You can view them ",
|
"message": "We had trouble loading your token balances. You can view them ",
|
||||||
"description": "Followed by a link (here) to view token balances"
|
"description": "Followed by a link (here) to view token balances"
|
||||||
},
|
},
|
||||||
|
"tryAgain": {
|
||||||
|
"message": "Try again"
|
||||||
|
},
|
||||||
"twelveWords": {
|
"twelveWords": {
|
||||||
"message": "These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret."
|
"message": "These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret."
|
||||||
},
|
},
|
||||||
@ -1069,7 +1093,7 @@
|
|||||||
"message": "Welcome to the New UI (Beta)"
|
"message": "Welcome to the New UI (Beta)"
|
||||||
},
|
},
|
||||||
"uiWelcomeMessage": {
|
"uiWelcomeMessage": {
|
||||||
"message": "You are now using the new Metamask UI."
|
"message": "You are now using the new MetaMask UI."
|
||||||
},
|
},
|
||||||
"unapproved": {
|
"unapproved": {
|
||||||
"message": "Unapproved"
|
"message": "Unapproved"
|
||||||
@ -1089,6 +1113,15 @@
|
|||||||
"unknownNetworkId": {
|
"unknownNetworkId": {
|
||||||
"message": "Unknown network ID"
|
"message": "Unknown network ID"
|
||||||
},
|
},
|
||||||
|
"unknownQrCode": {
|
||||||
|
"message": "Error: We couldn't identify that QR code"
|
||||||
|
},
|
||||||
|
"unknownCameraErrorTitle": {
|
||||||
|
"message": "Ooops! Something went wrong...."
|
||||||
|
},
|
||||||
|
"unknownCameraError": {
|
||||||
|
"message": "There was an error while trying to access you camera. Please try again..."
|
||||||
|
},
|
||||||
"unlock": {
|
"unlock": {
|
||||||
"message": "Unlock"
|
"message": "Unlock"
|
||||||
},
|
},
|
||||||
@ -1135,6 +1168,9 @@
|
|||||||
"whatsThis": {
|
"whatsThis": {
|
||||||
"message": "What's this?"
|
"message": "What's this?"
|
||||||
},
|
},
|
||||||
|
"youNeedToAllowCameraAccess": {
|
||||||
|
"message": "You need to allow camera access to use this feature."
|
||||||
|
},
|
||||||
"yourSigRequested": {
|
"yourSigRequested": {
|
||||||
"message": "Your signature is being requested"
|
"message": "Your signature is being requested"
|
||||||
},
|
},
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
"message": "Pedir prestado con Dharma (Beta)"
|
"message": "Pedir prestado con Dharma (Beta)"
|
||||||
},
|
},
|
||||||
"builtInCalifornia": {
|
"builtInCalifornia": {
|
||||||
"message": "Metamask fue diseñado y construido en California"
|
"message": "MetaMask fue diseñado y construido en California"
|
||||||
},
|
},
|
||||||
"buy": {
|
"buy": {
|
||||||
"message": "Comprar"
|
"message": "Comprar"
|
||||||
@ -874,7 +874,7 @@
|
|||||||
"message": "Advertencia"
|
"message": "Advertencia"
|
||||||
},
|
},
|
||||||
"welcomeBeta": {
|
"welcomeBeta": {
|
||||||
"message": "Bienvenido a Metamask Beta"
|
"message": "Bienvenido a MetaMask Beta"
|
||||||
},
|
},
|
||||||
"whatsThis": {
|
"whatsThis": {
|
||||||
"message": "¿Qué es esto?"
|
"message": "¿Qué es esto?"
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
"message": "Acheter sur Coinbase"
|
"message": "Acheter sur Coinbase"
|
||||||
},
|
},
|
||||||
"buyCoinbaseExplainer": {
|
"buyCoinbaseExplainer": {
|
||||||
"message": "Coinbase est le moyen le plus populaire au monde d'acheter et de vendre du bitcoin, de l'ethereum et du litecoin."
|
"message": "Coinbase est le moyen le plus populaire au monde d'acheter et de vendre du Bitcoin, de l'Ethereum et du Litecoin."
|
||||||
},
|
},
|
||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Annuler"
|
"message": "Annuler"
|
||||||
@ -570,7 +570,7 @@
|
|||||||
"message": "Bienvenue dans la nouvelle interface utilisateur (Beta)"
|
"message": "Bienvenue dans la nouvelle interface utilisateur (Beta)"
|
||||||
},
|
},
|
||||||
"uiWelcomeMessage": {
|
"uiWelcomeMessage": {
|
||||||
"message": "Vous utilisez maintenant la nouvelle interface utilisateur Metamask. Jetez un coup d'oeil, essayez de nouvelles fonctionnalités comme l'envoi de jetons, et faites-nous savoir si vous avez des problèmes."
|
"message": "Vous utilisez maintenant la nouvelle interface utilisateur MetaMask. Jetez un coup d'oeil, essayez de nouvelles fonctionnalités comme l'envoi de jetons, et faites-nous savoir si vous avez des problèmes."
|
||||||
},
|
},
|
||||||
"unavailable": {
|
"unavailable": {
|
||||||
"message": "Indisponible"
|
"message": "Indisponible"
|
||||||
|
@ -81,7 +81,7 @@
|
|||||||
"message": "Compra su Coinbase"
|
"message": "Compra su Coinbase"
|
||||||
},
|
},
|
||||||
"buyCoinbaseExplainer": {
|
"buyCoinbaseExplainer": {
|
||||||
"message": "Coinbase è il servizio più popolare al mondo per comprare e vendere bitcoin, ethereum e litecoin."
|
"message": "Coinbase è il servizio più popolare al mondo per comprare e vendere Bitcoin, Ethereum e Litecoin."
|
||||||
},
|
},
|
||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Cancella"
|
"message": "Cancella"
|
||||||
@ -178,7 +178,7 @@
|
|||||||
"message": "La rete predefinita per transazioni in Ether è la Rete Ethereum Principale."
|
"message": "La rete predefinita per transazioni in Ether è la Rete Ethereum Principale."
|
||||||
},
|
},
|
||||||
"denExplainer": {
|
"denExplainer": {
|
||||||
"message": "Il DEN è il tuo archivio crittato con password dentro Metamask."
|
"message": "Il DEN è il tuo archivio crittato con password dentro MetaMask."
|
||||||
},
|
},
|
||||||
"deposit": {
|
"deposit": {
|
||||||
"message": "Deposita"
|
"message": "Deposita"
|
||||||
@ -816,4 +816,4 @@
|
|||||||
"youSign": {
|
"youSign": {
|
||||||
"message": "Ti stai connettendo"
|
"message": "Ti stai connettendo"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@
|
|||||||
"message": "Koop op Coinbase"
|
"message": "Koop op Coinbase"
|
||||||
},
|
},
|
||||||
"buyCoinbaseExplainer": {
|
"buyCoinbaseExplainer": {
|
||||||
"message": "Coinbase is 's werelds populairste manier om bitcoin, ethereum en litecoin te kopen en verkopen."
|
"message": "Coinbase is 's werelds populairste manier om Bitcoin, Ethereum en Litecoin te kopen en verkopen."
|
||||||
},
|
},
|
||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Annuleer"
|
"message": "Annuleer"
|
||||||
@ -435,7 +435,7 @@
|
|||||||
"message": "back-up woorden hebben alleen kleine letters"
|
"message": "back-up woorden hebben alleen kleine letters"
|
||||||
},
|
},
|
||||||
"mainnet": {
|
"mainnet": {
|
||||||
"message": "belangrijkste ethereum-netwerk"
|
"message": "belangrijkste Ethereum-netwerk"
|
||||||
},
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"message": "Bericht"
|
"message": "Bericht"
|
||||||
@ -762,7 +762,7 @@
|
|||||||
"message": "Welkom bij de nieuwe gebruikersinterface (bèta)"
|
"message": "Welkom bij de nieuwe gebruikersinterface (bèta)"
|
||||||
},
|
},
|
||||||
"uiWelcomeMessage": {
|
"uiWelcomeMessage": {
|
||||||
"message": "U gebruikt nu de nieuwe gebruikersinterface van Metamask. Kijk rond, probeer nieuwe functies uit zoals het verzenden van tokens en laat ons weten of u problemen ondervindt."
|
"message": "U gebruikt nu de nieuwe gebruikersinterface van MetaMask. Kijk rond, probeer nieuwe functies uit zoals het verzenden van tokens en laat ons weten of u problemen ondervindt."
|
||||||
},
|
},
|
||||||
"unavailable": {
|
"unavailable": {
|
||||||
"message": "Niet beschikbaar"
|
"message": "Niet beschikbaar"
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
"message": "Bumili sa Coinbase"
|
"message": "Bumili sa Coinbase"
|
||||||
},
|
},
|
||||||
"buyCoinbaseExplainer": {
|
"buyCoinbaseExplainer": {
|
||||||
"message": "Ang Coinbase ang pinakasikat na paraan upang bumili at magbenta ng bitcoin, ethereum, at litecoin sa buong mundo."
|
"message": "Ang Coinbase ang pinakasikat na paraan upang bumili at magbenta ng Bitcoin, Ethereum, at Litecoin sa buong mundo."
|
||||||
},
|
},
|
||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Kanselahin"
|
"message": "Kanselahin"
|
||||||
|
@ -81,7 +81,7 @@
|
|||||||
"message": "Comprar no Coinbase"
|
"message": "Comprar no Coinbase"
|
||||||
},
|
},
|
||||||
"buyCoinbaseExplainer": {
|
"buyCoinbaseExplainer": {
|
||||||
"message": "Coinbase é a forma mais conhecida para comprar e vender bitcoin, ethereum, e litecoin."
|
"message": "Coinbase é a forma mais conhecida para comprar e vender Bitcoin, Ethereum, e Litecoin."
|
||||||
},
|
},
|
||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Cancelar"
|
"message": "Cancelar"
|
||||||
|
@ -84,7 +84,7 @@
|
|||||||
"message": "Купить на Coinbase"
|
"message": "Купить на Coinbase"
|
||||||
},
|
},
|
||||||
"buyCoinbaseExplainer": {
|
"buyCoinbaseExplainer": {
|
||||||
"message": "Биржа Coinbase – это наиболее популярный способ купить или продать bitcoin, ethereum и litecoin."
|
"message": "Биржа Coinbase – это наиболее популярный способ купить или продать Bitcoin, Ethereum и Litecoin."
|
||||||
},
|
},
|
||||||
"ok": {
|
"ok": {
|
||||||
"message": "ОК"
|
"message": "ОК"
|
||||||
|
@ -762,7 +762,7 @@
|
|||||||
"message": "ยินดีต้อนรับสู่หน้าตาใหม่ (เบต้า)"
|
"message": "ยินดีต้อนรับสู่หน้าตาใหม่ (เบต้า)"
|
||||||
},
|
},
|
||||||
"uiWelcomeMessage": {
|
"uiWelcomeMessage": {
|
||||||
"message": "ขณะนี้คุณใช้งาน Metamask หน้าตาใหม่แล้ว ลองใช้ความสามรถใหม่ ๆ เช่นการส่งโทเค็นและหากพบปัญหากรุณาแจ้งให้เราทราบ"
|
"message": "ขณะนี้คุณใช้งาน MetaMask หน้าตาใหม่แล้ว ลองใช้ความสามรถใหม่ ๆ เช่นการส่งโทเค็นและหากพบปัญหากรุณาแจ้งให้เราทราบ"
|
||||||
},
|
},
|
||||||
"unavailable": {
|
"unavailable": {
|
||||||
"message": "ใช้งานไม่ได้"
|
"message": "ใช้งานไม่ได้"
|
||||||
|
@ -84,7 +84,7 @@
|
|||||||
"message": "Coinbase'de satın al"
|
"message": "Coinbase'de satın al"
|
||||||
},
|
},
|
||||||
"buyCoinbaseExplainer": {
|
"buyCoinbaseExplainer": {
|
||||||
"message": "Coinbase bitcoin, ethereum, and litecoin alıp satmanın dünyadaki en popüler yolu"
|
"message": "Coinbase Bitcoin, Ethereum, and Litecoin alıp satmanın dünyadaki en popüler yolu"
|
||||||
},
|
},
|
||||||
"ok": {
|
"ok": {
|
||||||
"message": "Tamam"
|
"message": "Tamam"
|
||||||
@ -852,7 +852,7 @@
|
|||||||
"message": "Yeni UI (Beta)'ya hoşgeldiniz"
|
"message": "Yeni UI (Beta)'ya hoşgeldiniz"
|
||||||
},
|
},
|
||||||
"uiWelcomeMessage": {
|
"uiWelcomeMessage": {
|
||||||
"message": "Şu anda yeni Metamask UI kullanmaktasınız. Gözatın, jeton gönderme gibi yeni özellikleri deneyin ve herhangi bir sorunlar karşılaşırsanız bize haber verin"
|
"message": "Şu anda yeni MetaMask UI kullanmaktasınız. Gözatın, jeton gönderme gibi yeni özellikleri deneyin ve herhangi bir sorunlar karşılaşırsanız bize haber verin"
|
||||||
},
|
},
|
||||||
"unapproved": {
|
"unapproved": {
|
||||||
"message": "Onaylanmadı"
|
"message": "Onaylanmadı"
|
||||||
@ -909,4 +909,4 @@
|
|||||||
"youSign": {
|
"youSign": {
|
||||||
"message": "İmzalıyorsunuz"
|
"message": "İmzalıyorsunuz"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -570,7 +570,7 @@
|
|||||||
"message": "Chào mừng bạn đến với giao diện mới (Beta)"
|
"message": "Chào mừng bạn đến với giao diện mới (Beta)"
|
||||||
},
|
},
|
||||||
"uiWelcomeMessage": {
|
"uiWelcomeMessage": {
|
||||||
"message": "Bạn đang sử dụng giao diện mới của Metamask. Chúng tôi khuyến khích bạn thử nghiệm và khám phá các tính năng mới như gửi token, và nếu bạn có gặp phải vấn đề gì khó khăn, xin hãy liên hệ ngay để chúng tôi có thể giúp đỡ bạn."
|
"message": "Bạn đang sử dụng giao diện mới của MetaMask. Chúng tôi khuyến khích bạn thử nghiệm và khám phá các tính năng mới như gửi token, và nếu bạn có gặp phải vấn đề gì khó khăn, xin hãy liên hệ ngay để chúng tôi có thể giúp đỡ bạn."
|
||||||
},
|
},
|
||||||
"unavailable": {
|
"unavailable": {
|
||||||
"message": "Không có sẵn"
|
"message": "Không có sẵn"
|
||||||
|
@ -879,7 +879,7 @@
|
|||||||
"message": "欢迎使用新版界面 (Beta)"
|
"message": "欢迎使用新版界面 (Beta)"
|
||||||
},
|
},
|
||||||
"uiWelcomeMessage": {
|
"uiWelcomeMessage": {
|
||||||
"message": "你现在正在使用新的 Metamask 界面。 尝试发送代币等新功能,有任何问题请告知我们。"
|
"message": "你现在正在使用新的 MetaMask 界面。 尝试发送代币等新功能,有任何问题请告知我们。"
|
||||||
},
|
},
|
||||||
"unapproved": {
|
"unapproved": {
|
||||||
"message": "未批准"
|
"message": "未批准"
|
||||||
|
@ -362,7 +362,7 @@
|
|||||||
"message": "你想怎麼存入 Ether?"
|
"message": "你想怎麼存入 Ether?"
|
||||||
},
|
},
|
||||||
"holdEther": {
|
"holdEther": {
|
||||||
"message": "Metamask 讓您能保存 ether 和代幣, 並成為您接觸分散式應用程式的途徑."
|
"message": "MetaMask 讓您能保存 ether 和代幣, 並成為您接觸分散式應用程式的途徑."
|
||||||
},
|
},
|
||||||
"import": {
|
"import": {
|
||||||
"message": "導入",
|
"message": "導入",
|
||||||
|
1
app/images/ledger-logo.svg
Normal file
1
app/images/ledger-logo.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1916.3 516.8" width="2500" height="674"><style>.st0{fill:#333745}</style><g id="squares_1_"><path class="st0" d="M578.2 392.7V24.3h25.6v344.1h175.3v24.3H578.2zm327.5 5.1c-39.7 0-70.4-12.8-93.4-37.1-21.7-24.3-33.3-58.8-33.3-103.6 0-43.5 10.2-79.3 32-104.9 21.7-26.9 49.9-39.7 87-39.7 32 0 57.6 11.5 76.8 33.3 19.2 23 28.1 53.7 28.1 92.1v20.5H804.6c0 37.1 9 66.5 26.9 85.7 16.6 20.5 42.2 29.4 74.2 29.4 15.3 0 29.4-1.3 40.9-3.8 11.5-2.6 26.9-6.4 44.8-14.1v24.3c-15.3 6.4-29.4 11.5-42.2 14.1-14.3 2.6-28.9 3.9-43.5 3.8zM898 135.6c-26.9 0-47.3 9-64 25.6-15.3 17.9-25.6 42.2-28.1 75.5h168.9c0-32-6.4-56.3-20.5-74.2-12.8-18-32-26.9-56.3-26.9zm238-21.8c19.2 0 37.1 3.8 51.2 10.2 14.1 7.7 26.9 19.2 38.4 37.1h1.3c-1.3-21.7-1.3-42.2-1.3-62.7V0h24.3v392.7h-16.6l-6.4-42.2c-20.5 30.7-51.2 47.3-89.6 47.3s-66.5-11.5-87-35.8c-20.5-23-29.4-57.6-29.4-102.3 0-47.3 10.2-83.2 29.4-108.7 19.2-25.6 48.6-37.2 85.7-37.2zm0 21.8c-29.4 0-52.4 10.2-67.8 32-15.3 20.5-23 51.2-23 92.1 0 78 30.7 116.4 90.8 116.4 30.7 0 53.7-9 67.8-26.9 14.1-17.9 21.7-47.3 21.7-89.6v-3.8c0-42.2-7.7-72.9-21.7-90.8-12.8-20.5-35.8-29.4-67.8-29.4zm379.9-16.6v17.9l-56.3 3.8c15.3 19.2 23 39.7 23 61.4 0 26.9-9 47.3-26.9 64-17.9 16.6-40.9 24.3-70.4 24.3-12.8 0-21.7 0-25.6-1.3-10.2 5.1-17.9 11.5-23 17.9-5.1 7.7-7.7 14.1-7.7 23s3.8 15.3 10.2 19.2c6.4 3.8 17.9 6.4 33.3 6.4h47.3c29.4 0 52.4 6.4 67.8 17.9s24.3 29.4 24.3 53.7c0 29.4-11.5 51.2-34.5 66.5-23 15.3-56.3 23-99.8 23-34.5 0-61.4-6.4-80.6-20.5-19.2-12.8-28.1-32-28.1-55 0-19.2 6.4-34.5 17.9-47.3s28.1-20.5 47.3-25.6c-7.7-3.8-15.3-9-19.2-15.3-5-6.2-7.7-13.8-7.7-21.7 0-17.9 11.5-34.5 34.5-48.6-15.3-6.4-28.1-16.6-37.1-30.7-9-14.1-12.8-30.7-12.8-48.6 0-26.9 9-49.9 25.6-66.5 17.9-16.6 40.9-24.3 70.4-24.3 17.9 0 32 1.3 42.2 5.1h85.7v1.3h.2zm-222.6 319.8c0 37.1 28.1 56.3 84.4 56.3 71.6 0 107.5-23 107.5-69.1 0-16.6-5.1-28.1-16.6-35.8-11.5-7.7-29.4-11.5-55-11.5h-44.8c-49.9 1.2-75.5 20.4-75.5 60.1zm21.8-235.4c0 21.7 6.4 37.1 19.2 49.9 12.8 11.5 29.4 17.9 51.2 17.9 23 0 40.9-6.4 52.4-17.9 12.8-11.5 17.9-28.1 17.9-49.9 0-23-6.4-40.9-19.2-52.4-12.8-11.5-29.4-17.9-52.4-17.9-21.7 0-39.7 6.4-51.2 19.2-12.8 11.4-17.9 29.3-17.9 51.1z"/><path class="st0" d="M1640 397.8c-39.7 0-70.4-12.8-93.4-37.1-21.7-24.3-33.3-58.8-33.3-103.6 0-43.5 10.2-79.3 32-104.9 21.7-26.9 49.9-39.7 87-39.7 32 0 57.6 11.5 76.8 33.3 19.2 23 28.1 53.7 28.1 92.1v20.5h-197c0 37.1 9 66.5 26.9 85.7 16.6 20.5 42.2 29.4 74.2 29.4 15.3 0 29.4-1.3 40.9-3.8 11.5-2.6 26.9-6.4 44.8-14.1v24.3c-15.3 6.4-29.4 11.5-42.2 14.1-14.1 2.6-28.2 3.8-44.8 3.8zm-6.4-262.2c-26.9 0-47.3 9-64 25.6-15.3 17.9-25.6 42.2-28.1 75.5h168.9c0-32-6.4-56.3-20.5-74.2-12.8-18-32-26.9-56.3-26.9zm245.6-21.8c11.5 0 24.3 1.3 37.1 3.8l-5.1 24.3c-11.8-2.6-23.8-3.9-35.8-3.8-23 0-42.2 10.2-57.6 29.4-15.3 20.5-23 44.8-23 75.5v149.7h-25.6V119h21.7l2.6 49.9h1.3c11.5-20.5 23-34.5 35.8-42.2 15.4-9 30.7-12.9 48.6-12.9zM333.9 12.8h-183v245.6h245.6V76.7c.1-34.5-28.1-63.9-62.6-63.9zm-239.2 0H64c-34.5 0-64 28.1-64 64v30.7h94.7V12.8zM0 165h94.7v94.7H0V165zm301.9 245.6h30.7c34.5 0 64-28.1 64-64V316h-94.7v94.6zm-151-94.6h94.7v94.7h-94.7V316zM0 316v30.7c0 34.5 28.1 64 64 64h30.7V316H0z"/></g></svg>
|
After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
115
app/images/logo/metamask-logo-horizontal-beta.svg
Normal file
115
app/images/logo/metamask-logo-horizontal-beta.svg
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns:ev="http://www.w3.org/2001/xml-events"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1672.1 241.2"
|
||||||
|
style="enable-background:new 0 0 1672.1 241.2;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#161616;}
|
||||||
|
.st1{fill:#E17726;stroke:#E17726;stroke-linecap:round;stroke-linejoin:round;}
|
||||||
|
.st2{fill:#E27625;stroke:#E27625;stroke-linecap:round;stroke-linejoin:round;}
|
||||||
|
.st3{fill:#D5BFB2;stroke:#D5BFB2;stroke-linecap:round;stroke-linejoin:round;}
|
||||||
|
.st4{fill:#233447;stroke:#233447;stroke-linecap:round;stroke-linejoin:round;}
|
||||||
|
.st5{fill:#CC6228;stroke:#CC6228;stroke-linecap:round;stroke-linejoin:round;}
|
||||||
|
.st6{fill:#E27525;stroke:#E27525;stroke-linecap:round;stroke-linejoin:round;}
|
||||||
|
.st7{fill:#F5841F;stroke:#F5841F;stroke-linecap:round;stroke-linejoin:round;}
|
||||||
|
.st8{fill:#C0AC9D;stroke:#C0AC9D;stroke-linecap:round;stroke-linejoin:round;}
|
||||||
|
.st9{fill:#161616;stroke:#161616;stroke-linecap:round;stroke-linejoin:round;}
|
||||||
|
.st10{fill:#763E1A;stroke:#763E1A;stroke-linecap:round;stroke-linejoin:round;}
|
||||||
|
.st11{fill:#F5841F;}
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M1157.7,121.9c-6.8-4.5-14.3-7.7-21.4-11.7c-4.6-2.6-9.5-4.9-13.5-8.2c-6.8-5.6-5.4-16.6,1.7-21.4
|
||||||
|
c10.2-6.8,27.1-3,28.9,10.9c0,0.3,0.3,0.5,0.6,0.5h15.4c0.4,0,0.7-0.3,0.6-0.7c-0.8-9.6-4.5-17.6-11.3-22.7
|
||||||
|
c-6.5-4.9-13.9-7.5-21.8-7.5c-40.7,0-44.4,43.1-22.5,56.7c2.5,1.6,24,12.4,31.6,17.1s10,13.3,6.7,20.1c-3,6.2-10.8,10.5-18.6,10
|
||||||
|
c-8.5-0.5-15.1-5.1-17.4-12.3c-0.4-1.3-0.6-3.8-0.6-4.9c0-0.3-0.3-0.6-0.6-0.6h-16.7c-0.3,0-0.6,0.3-0.6,0.6
|
||||||
|
c0,12.1,3,18.8,11.2,24.9c7.7,5.8,16.1,8.2,24.8,8.2c22.8,0,34.6-12.9,37-26.3C1173.3,141.5,1169.4,129.7,1157.7,121.9z"/>
|
||||||
|
<path class="st0" d="M432.6,63.3h-7.4h-8.1c-0.3,0-0.5,0.2-0.6,0.4l-13.7,45.2c-0.2,0.6-1,0.6-1.2,0l-13.7-45.2
|
||||||
|
c-0.1-0.3-0.3-0.4-0.6-0.4h-8.1h-7.4h-10c-0.3,0-0.6,0.3-0.6,0.6v115.4c0,0.3,0.3,0.6,0.6,0.6h16.7c0.3,0,0.6-0.3,0.6-0.6V91.6
|
||||||
|
c0-0.7,1-0.8,1.2-0.2l13.8,45.5l1,3.2c0.1,0.3,0.3,0.4,0.6,0.4h12.8c0.3,0,0.5-0.2,0.6-0.4l1-3.2l13.8-45.5
|
||||||
|
c0.2-0.7,1.2-0.5,1.2,0.2v87.7c0,0.3,0.3,0.6,0.6,0.6h16.7c0.3,0,0.6-0.3,0.6-0.6V63.9c0-0.3-0.3-0.6-0.6-0.6L432.6,63.3
|
||||||
|
L432.6,63.3z"/>
|
||||||
|
<path class="st0" d="M902,63.3c-0.3,0-0.5,0.2-0.6,0.4l-13.7,45.2c-0.2,0.6-1,0.6-1.2,0l-13.7-45.2c-0.1-0.3-0.3-0.4-0.6-0.4h-25.4
|
||||||
|
c-0.3,0-0.6,0.3-0.6,0.6v115.4c0,0.3,0.3,0.6,0.6,0.6h16.7c0.3,0,0.6-0.3,0.6-0.6V91.6c0-0.7,1-0.8,1.2-0.2l13.8,45.5l1,3.2
|
||||||
|
c0.1,0.3,0.3,0.4,0.6,0.4h12.8c0.3,0,0.5-0.2,0.6-0.4l1-3.2l13.8-45.5c0.2-0.7,1.2-0.5,1.2,0.2v87.7c0,0.3,0.3,0.6,0.6,0.6h16.7
|
||||||
|
c0.3,0,0.6-0.3,0.6-0.6V63.9c0-0.3-0.3-0.6-0.6-0.6L902,63.3L902,63.3z"/>
|
||||||
|
<path class="st0" d="M686.6,63.3h-31.1h-16.7h-31.1c-0.3,0-0.6,0.3-0.6,0.6v14.4c0,0.3,0.3,0.6,0.6,0.6h30.5v100.4
|
||||||
|
c0,0.3,0.3,0.6,0.6,0.6h16.7c0.3,0,0.6-0.3,0.6-0.6V78.9h30.5c0.3,0,0.6-0.3,0.6-0.6V63.9C687.2,63.6,687,63.3,686.6,63.3z"/>
|
||||||
|
<path class="st0" d="M785.1,179.9h15.2c0.4,0,0.7-0.4,0.6-0.8L769.5,63.3c-0.1-0.3-0.3-0.4-0.6-0.4h-5.8h-10.2h-5.8
|
||||||
|
c-0.3,0-0.5,0.2-0.6,0.4l-31.4,115.8c-0.1,0.4,0.2,0.8,0.6,0.8h15.2c0.3,0,0.5-0.2,0.6-0.4l9.1-33.7c0.1-0.3,0.3-0.4,0.6-0.4h33.6
|
||||||
|
c0.3,0,0.5,0.2,0.6,0.4l9.1,33.7C784.6,179.7,784.9,179.9,785.1,179.9z M745.2,128.9l12.2-45.1c0.2-0.6,1-0.6,1.2,0l12.2,45.1
|
||||||
|
c0.1,0.4-0.2,0.8-0.6,0.8h-24.4C745.4,129.7,745.1,129.3,745.2,128.9z"/>
|
||||||
|
<path class="st0" d="M1044.3,179.9h15.2c0.4,0,0.7-0.4,0.6-0.8l-31.4-115.8c-0.1-0.3-0.3-0.4-0.6-0.4h-5.8h-10.2h-5.8
|
||||||
|
c-0.3,0-0.5,0.2-0.6,0.4l-31.4,115.8c-0.1,0.4,0.2,0.8,0.6,0.8h15.2c0.3,0,0.5-0.2,0.6-0.4l9.1-33.7c0.1-0.3,0.3-0.4,0.6-0.4h33.6
|
||||||
|
c0.3,0,0.5,0.2,0.6,0.4l9.1,33.7C1043.8,179.7,1044,179.9,1044.3,179.9z M1004.4,128.9l12.2-45.1c0.2-0.6,1-0.6,1.2,0l12.2,45.1
|
||||||
|
c0.1,0.4-0.2,0.8-0.6,0.8H1005C1004.6,129.7,1004.3,129.3,1004.4,128.9z"/>
|
||||||
|
<path class="st0" d="M510.8,162.8V127c0-0.3,0.3-0.6,0.6-0.6h44.5c0.3,0,0.6-0.3,0.6-0.6v-14.4c0-0.3-0.3-0.6-0.6-0.6h-44.5
|
||||||
|
c-0.3,0-0.6-0.3-0.6-0.6V79.6c0-0.3,0.3-0.6,0.6-0.6H562c0.3,0,0.6-0.3,0.6-0.6V64c0-0.3-0.3-0.6-0.6-0.6h-51.2h-17.3
|
||||||
|
c-0.3,0-0.6,0.3-0.6,0.6v15v31.9v15.6v37v15.8c0,0.3,0.3,0.6,0.6,0.6h17.3h53.3c0.3,0,0.6-0.3,0.6-0.6v-15.2c0-0.3-0.3-0.6-0.6-0.6
|
||||||
|
h-52.8C511,163.4,510.8,163.2,510.8,162.8z"/>
|
||||||
|
<path class="st0" d="M1310.3,178.9l-57.8-59.7c-0.2-0.2-0.2-0.6,0-0.8l52-54c0.4-0.4,0.1-1-0.4-1h-21.3c-0.2,0-0.3,0.1-0.4,0.2
|
||||||
|
l-44.1,45.8c-0.4,0.4-1,0.1-1-0.4V64c0-0.3-0.3-0.6-0.6-0.6H1220c-0.3,0-0.6,0.3-0.6,0.6v115.4c0,0.3,0.3,0.6,0.6,0.6h16.7
|
||||||
|
c0.3,0,0.6-0.3,0.6-0.6v-50.8c0-0.5,0.7-0.8,1-0.4l50,51.6c0.1,0.1,0.3,0.2,0.4,0.2h21.3C1310.4,179.9,1310.7,179.2,1310.3,178.9z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polygon class="st1" points="247.1,1.2 146,76.2 164.8,32 "/>
|
||||||
|
<g>
|
||||||
|
<polygon class="st2" points="13.9,1.2 114.1,76.9 96.2,32 "/>
|
||||||
|
<polygon class="st2" points="210.7,175.1 183.8,216.3 241.4,232.2 257.9,176 "/>
|
||||||
|
<polygon class="st2" points="3.2,176 19.6,232.2 77.1,216.3 50.3,175.1 "/>
|
||||||
|
<polygon class="st2" points="74,105.5 58,129.7 115,132.3 113.1,70.8 "/>
|
||||||
|
<polygon class="st2" points="187,105.5 147.3,70.1 146,132.3 203,129.7 "/>
|
||||||
|
<polygon class="st2" points="77.1,216.3 111.6,199.6 81.9,176.4 "/>
|
||||||
|
<polygon class="st2" points="149.4,199.6 183.8,216.3 179.1,176.4 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polygon class="st3" points="183.8,216.3 149.4,199.6 152.2,222 151.9,231.5 "/>
|
||||||
|
<polygon class="st3" points="77.1,216.3 109.1,231.5 108.9,222 111.6,199.6 "/>
|
||||||
|
</g>
|
||||||
|
<polygon class="st4" points="109.7,161.6 81.1,153.2 101.3,143.9 "/>
|
||||||
|
<polygon class="st4" points="151.3,161.6 159.7,143.9 180,153.2 "/>
|
||||||
|
<g>
|
||||||
|
<polygon class="st5" points="77.1,216.3 82.1,175.1 50.3,176 "/>
|
||||||
|
<polygon class="st5" points="178.9,175.1 183.8,216.3 210.7,176 "/>
|
||||||
|
<polygon class="st5" points="203,129.7 146,132.3 151.3,161.6 159.7,143.9 180,153.2 "/>
|
||||||
|
<polygon class="st5" points="81.1,153.2 101.3,143.9 109.7,161.6 115,132.3 58,129.7 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polygon class="st6" points="58,129.7 81.9,176.4 81.1,153.2 "/>
|
||||||
|
<polygon class="st6" points="180,153.2 179.1,176.4 203,129.7 "/>
|
||||||
|
<polygon class="st6" points="115,132.3 109.7,161.6 116.4,196.2 117.9,150.6 "/>
|
||||||
|
<polygon class="st6" points="146,132.3 143.2,150.5 144.6,196.2 151.3,161.6 "/>
|
||||||
|
</g>
|
||||||
|
<polygon class="st7" points="151.3,161.6 144.6,196.2 149.4,199.6 179.1,176.4 180,153.2 "/>
|
||||||
|
<polygon class="st7" points="81.1,153.2 81.9,176.4 111.6,199.6 116.4,196.2 109.7,161.6 "/>
|
||||||
|
<polygon class="st8" points="151.9,231.5 152.2,222 149.6,219.8 111.4,219.8 108.9,222 109.1,231.5 77.1,216.3 88.3,225.5
|
||||||
|
111,241.2 149.9,241.2 172.7,225.5 183.8,216.3 "/>
|
||||||
|
<polygon class="st9" points="149.4,199.6 144.6,196.2 116.4,196.2 111.6,199.6 108.9,222 111.4,219.8 149.6,219.8 152.2,222 "/>
|
||||||
|
<g>
|
||||||
|
<polygon class="st10" points="251.4,81.1 259.9,39.7 247.1,1.2 149.4,73.7 187,105.5 240.1,121 251.8,107.3 246.7,103.6
|
||||||
|
254.8,96.2 248.6,91.4 256.7,85.2 "/>
|
||||||
|
<polygon class="st10" points="1.1,39.7 9.7,81.1 4.2,85.2 12.4,91.4 6.2,96.2 14.3,103.6 9.2,107.3 20.9,121 74,105.5 111.6,73.7
|
||||||
|
13.9,1.2 "/>
|
||||||
|
</g>
|
||||||
|
<polygon class="st7" points="240.1,121 187,105.5 203,129.7 179.1,176.4 210.7,176 257.9,176 "/>
|
||||||
|
<polygon class="st7" points="74,105.5 20.9,121 3.2,176 50.3,176 81.9,176.4 58,129.7 "/>
|
||||||
|
<polygon class="st7" points="146,132.3 149.4,73.7 164.8,32 96.2,32 111.6,73.7 115,132.3 116.3,150.7 116.4,196.2 144.6,196.2
|
||||||
|
144.7,150.7 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st7" d="M1409.7,92.6V32.1h19.3c2.9,0,5.6,0.4,8.2,1c2.5,0.6,4.7,1.6,6.6,2.9c1.9,1.3,3.4,3,4.5,5.1
|
||||||
|
c1.1,2.1,1.6,4.5,1.6,7.3c0,3-0.9,5.5-2.5,7.7c-1.6,2.1-3.8,3.8-6.6,4.9c1.7,0.5,3.2,1.1,4.5,2c1.3,0.9,2.4,1.9,3.3,3.1
|
||||||
|
c0.9,1.2,1.6,2.6,2,4c0.5,1.5,0.7,3.1,0.7,4.7c0,2.9-0.5,5.4-1.6,7.6c-1.1,2.2-2.5,4-4.4,5.5c-1.9,1.5-4.1,2.6-6.6,3.3
|
||||||
|
c-2.6,0.8-5.3,1.2-8.3,1.2H1409.7z M1419.8,57.6h9.6c1.5,0,2.9-0.2,4.2-0.6c1.3-0.4,2.4-0.9,3.3-1.7c0.9-0.7,1.7-1.6,2.2-2.7
|
||||||
|
c0.6-1.1,0.8-2.3,0.8-3.7c0-1.5-0.3-2.8-0.8-3.9c-0.5-1.1-1.3-2-2.2-2.7c-1-0.7-2.1-1.2-3.4-1.5c-1.3-0.3-2.7-0.5-4.3-0.5h-9.5
|
||||||
|
V57.6z M1419.8,65.2v19.3h10.9c1.6,0,3-0.3,4.3-0.7c1.3-0.5,2.4-1.1,3.4-1.9c0.9-0.8,1.7-1.8,2.2-2.9c0.5-1.2,0.8-2.5,0.8-3.9
|
||||||
|
c0-1.5-0.2-2.9-0.7-4.1c-0.5-1.2-1.1-2.2-2-3.1c-0.9-0.8-2-1.5-3.2-1.9c-1.3-0.5-2.7-0.7-4.3-0.7H1419.8z"/>
|
||||||
|
<path class="st7" d="M1506.3,65.5h-24.9v19h29.1v8.1h-39.1V32.1h38.8v8.2h-28.8v17.1h24.9V65.5z"/>
|
||||||
|
<path class="st7" d="M1574.8,40.4h-18.6v52.2h-9.9V40.4h-18.4v-8.2h46.9V40.4z"/>
|
||||||
|
<path class="st7" d="M1615.2,78.6h-18.9l-4.2,14h-10.3l19.6-60.5h8.8l19.3,60.5h-10.3L1615.2,78.6z M1598.9,70h13.9l-6.9-23.7
|
||||||
|
L1598.9,70z"/>
|
||||||
|
</g>
|
||||||
|
<path class="st11" d="M1644.3,8c10.7,0,19.5,8.7,19.5,19.5v69.8c0,10.7-8.7,19.5-19.5,19.5H1395c-10.7,0-19.5-8.7-19.5-19.5V27.5
|
||||||
|
c0-10.7,8.7-19.5,19.5-19.5H1644.3 M1644.3,0H1395c-15.2,0-27.5,12.3-27.5,27.5v69.8c0,15.2,12.3,27.5,27.5,27.5h249.2
|
||||||
|
c15.2,0,27.5-12.3,27.5-27.5V27.5C1671.7,12.3,1659.4,0,1644.3,0L1644.3,0z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 8.7 KiB |
1
app/images/trezor-logo.svg
Normal file
1
app/images/trezor-logo.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg id="trezor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2567.5 722.3" width="2567.5" height="722.3"><style>.st0{display:none;fill:#fff;stroke:#000}</style><path id="rect25" class="st0" d="M1186 2932.6h46.2v147H1186v-147z"/><path id="path7" d="M249 0C149.9 0 69.7 80.2 69.7 179.3v67.2C34.9 252.8 0 261.2 0 272.1v350.7s0 9.7 10.9 14.3c39.5 16 194.9 71 230.6 83.6 4.6 1.7 5.9 1.7 7.1 1.7 1.7 0 2.5 0 7.1-1.7 35.7-12.6 191.5-67.6 231-83.6 10.1-4.2 10.5-13.9 10.5-13.9V272.1c0-10.9-34.4-19.7-69.3-25.6v-67.2C428.4 80.2 347.7 0 249 0zm0 85.7c58.4 0 93.7 35.3 93.7 93.7v58.4c-65.5-4.6-121.4-4.6-187.3 0v-58.4c0-58.5 35.3-93.7 93.6-93.7zm-.4 238.1c81.5 0 149.9 6.3 149.9 17.6v218.8c0 3.4-.4 3.8-3.4 5-2.9 1.3-139 50.4-139 50.4s-5.5 1.7-7.1 1.7c-1.7 0-7.1-2.1-7.1-2.1s-136.1-49.1-139-50.4-3.4-1.7-3.4-5V341c-.8-11.3 67.6-17.2 149.1-17.2z"/><g id="g3222" transform="translate(91.363 -287.434) scale(.95575)"><path id="path13" d="M666.6 890V639.3H575v-89.9h285.6v89.9h-90.7V890H666.6z"/><path id="path15" d="M1092 890l-47-107.1h-37.4V890H904.3V549.4h181.8c79.8 0 122.6 52.9 122.6 116.7 0 58.8-34 89.9-61.3 103.3l61.7 120.5H1092zm12.2-223.9c0-18.5-16.4-26.5-33.6-26.5h-63v53.8h63c17.2-.4 33.6-8.4 33.6-27.3z"/><path id="path17" d="M1262.9 890V549.4h258.3v89.9h-155.4v33.6h151.6v89.9h-151.6v37.4h155.4V890h-258.3z"/><path id="path19" d="M1574.9 890.4v-81.9l129.8-168.8h-129.8v-89.9h265.8v81.1l-130.2 169.7h134v89.9l-269.6-.1z"/><path id="path21" d="M1869.7 720.3c0-104.6 81.1-176.4 186.5-176.4 105 0 186.5 71.4 186.5 176.4 0 104.6-81.1 176-186.5 176s-186.5-71.4-186.5-176zm268 0c0-47.5-32.3-85.3-81.9-85.3-49.6 0-81.9 37.8-81.9 85.3s32.3 85.3 81.9 85.3c50 0 81.9-37.8 81.9-85.3z"/><path id="path23" d="M2473.6 890.4l-47-107.1h-37.4v107.1h-103.3V549.8h181.8c79.8 0 122.6 52.9 122.6 116.7 0 58.8-34 89.9-61.3 103.3l61.7 120.5h-117.1zm12.6-224.3c0-18.5-16.4-26.5-33.6-26.5h-63v53.8h63c17.3-.4 33.6-8.4 33.6-27.3z"/></g></svg>
|
After Width: | Height: | Size: 1.9 KiB |
18
app/images/webcam.svg
Normal file
18
app/images/webcam.svg
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="53px" height="53px" viewBox="0 0 53 53" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>webcam</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="QR-Code-Scan" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Group-4-Copy" transform="translate(-482.000000, -218.000000)">
|
||||||
|
<g id="webcam" transform="translate(482.000000, 218.000000)">
|
||||||
|
<circle id="Oval" fill="#D5ECFA" cx="26.5" cy="26.5" r="26.5"></circle>
|
||||||
|
<g id="Group" transform="translate(14.000000, 19.000000)" fill="#259DE5">
|
||||||
|
<rect id="Rectangle" x="0" y="0" width="18" height="16"></rect>
|
||||||
|
<polygon id="Triangle" points="19 6.57142857 26 3 26 13 19 9.42857143"></polygon>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1020 B |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "__MSG_appName__",
|
"name": "__MSG_appName__",
|
||||||
"short_name": "__MSG_appName__",
|
"short_name": "__MSG_appName__",
|
||||||
"version": "4.9.1",
|
"version": "4.9.3",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"author": "https://metamask.io",
|
"author": "https://metamask.io",
|
||||||
"description": "__MSG_appDescription__",
|
"description": "__MSG_appDescription__",
|
||||||
|
@ -19,7 +19,7 @@ const PortStream = require('./lib/port-stream.js')
|
|||||||
const createStreamSink = require('./lib/createStreamSink')
|
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 rawFirstTimeState = require('./first-time-state')
|
||||||
const setupRaven = require('./lib/setupRaven')
|
const setupRaven = require('./lib/setupRaven')
|
||||||
const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry')
|
const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry')
|
||||||
const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
|
const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
|
||||||
@ -34,6 +34,9 @@ const {
|
|||||||
ENVIRONMENT_TYPE_FULLSCREEN,
|
ENVIRONMENT_TYPE_FULLSCREEN,
|
||||||
} = require('./lib/enums')
|
} = require('./lib/enums')
|
||||||
|
|
||||||
|
// METAMASK_TEST_CONFIG is used in e2e tests to set the default network to localhost
|
||||||
|
const firstTimeState = Object.assign({}, rawFirstTimeState, global.METAMASK_TEST_CONFIG)
|
||||||
|
|
||||||
const STORAGE_KEY = 'metamask-config'
|
const STORAGE_KEY = 'metamask-config'
|
||||||
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
||||||
|
|
||||||
|
@ -198,6 +198,6 @@ function blacklistedDomainCheck () {
|
|||||||
*/
|
*/
|
||||||
function redirectToPhishingWarning () {
|
function redirectToPhishingWarning () {
|
||||||
console.log('MetaMask - routing to Phishing Warning component')
|
console.log('MetaMask - routing to Phishing Warning component')
|
||||||
let extensionURL = extension.runtime.getURL('phishing.html')
|
const extensionURL = extension.runtime.getURL('phishing.html')
|
||||||
window.location.href = extensionURL
|
window.location.href = extensionURL
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ class BalanceController {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.accountTracker.store.subscribe(update)
|
this.accountTracker.store.subscribe(update)
|
||||||
this.blockTracker.on('block', update)
|
this.blockTracker.on('latest', update)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
|
|
||||||
|
25
app/scripts/controllers/network/createInfuraClient.js
Normal file
25
app/scripts/controllers/network/createInfuraClient.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
|
||||||
|
const createBlockReEmitMiddleware = require('eth-json-rpc-middleware/block-reemit')
|
||||||
|
const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache')
|
||||||
|
const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache')
|
||||||
|
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
|
||||||
|
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
|
||||||
|
const createInfuraMiddleware = require('eth-json-rpc-infura')
|
||||||
|
const BlockTracker = require('eth-block-tracker')
|
||||||
|
|
||||||
|
module.exports = createInfuraClient
|
||||||
|
|
||||||
|
function createInfuraClient ({ network }) {
|
||||||
|
const infuraMiddleware = createInfuraMiddleware({ network })
|
||||||
|
const blockProvider = providerFromMiddleware(infuraMiddleware)
|
||||||
|
const blockTracker = new BlockTracker({ provider: blockProvider })
|
||||||
|
|
||||||
|
const networkMiddleware = mergeMiddleware([
|
||||||
|
createBlockCacheMiddleware({ blockTracker }),
|
||||||
|
createInflightMiddleware(),
|
||||||
|
createBlockReEmitMiddleware({ blockTracker, provider: blockProvider }),
|
||||||
|
createBlockTrackerInspectorMiddleware({ blockTracker }),
|
||||||
|
infuraMiddleware,
|
||||||
|
])
|
||||||
|
return { networkMiddleware, blockTracker }
|
||||||
|
}
|
25
app/scripts/controllers/network/createJsonRpcClient.js
Normal file
25
app/scripts/controllers/network/createJsonRpcClient.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
|
||||||
|
const createFetchMiddleware = require('eth-json-rpc-middleware/fetch')
|
||||||
|
const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref')
|
||||||
|
const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache')
|
||||||
|
const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache')
|
||||||
|
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
|
||||||
|
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
|
||||||
|
const BlockTracker = require('eth-block-tracker')
|
||||||
|
|
||||||
|
module.exports = createJsonRpcClient
|
||||||
|
|
||||||
|
function createJsonRpcClient ({ rpcUrl }) {
|
||||||
|
const fetchMiddleware = createFetchMiddleware({ rpcUrl })
|
||||||
|
const blockProvider = providerFromMiddleware(fetchMiddleware)
|
||||||
|
const blockTracker = new BlockTracker({ provider: blockProvider })
|
||||||
|
|
||||||
|
const networkMiddleware = mergeMiddleware([
|
||||||
|
createBlockRefMiddleware({ blockTracker }),
|
||||||
|
createBlockCacheMiddleware({ blockTracker }),
|
||||||
|
createInflightMiddleware(),
|
||||||
|
createBlockTrackerInspectorMiddleware({ blockTracker }),
|
||||||
|
fetchMiddleware,
|
||||||
|
])
|
||||||
|
return { networkMiddleware, blockTracker }
|
||||||
|
}
|
21
app/scripts/controllers/network/createLocalhostClient.js
Normal file
21
app/scripts/controllers/network/createLocalhostClient.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
|
||||||
|
const createFetchMiddleware = require('eth-json-rpc-middleware/fetch')
|
||||||
|
const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref')
|
||||||
|
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
|
||||||
|
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
|
||||||
|
const BlockTracker = require('eth-block-tracker')
|
||||||
|
|
||||||
|
module.exports = createLocalhostClient
|
||||||
|
|
||||||
|
function createLocalhostClient () {
|
||||||
|
const fetchMiddleware = createFetchMiddleware({ rpcUrl: 'http://localhost:8545/' })
|
||||||
|
const blockProvider = providerFromMiddleware(fetchMiddleware)
|
||||||
|
const blockTracker = new BlockTracker({ provider: blockProvider, pollingInterval: 1000 })
|
||||||
|
|
||||||
|
const networkMiddleware = mergeMiddleware([
|
||||||
|
createBlockRefMiddleware({ blockTracker }),
|
||||||
|
createBlockTrackerInspectorMiddleware({ blockTracker }),
|
||||||
|
fetchMiddleware,
|
||||||
|
])
|
||||||
|
return { networkMiddleware, blockTracker }
|
||||||
|
}
|
43
app/scripts/controllers/network/createMetamaskMiddleware.js
Normal file
43
app/scripts/controllers/network/createMetamaskMiddleware.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
|
||||||
|
const createScaffoldMiddleware = require('json-rpc-engine/src/createScaffoldMiddleware')
|
||||||
|
const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware')
|
||||||
|
const createWalletSubprovider = require('eth-json-rpc-middleware/wallet')
|
||||||
|
|
||||||
|
module.exports = createMetamaskMiddleware
|
||||||
|
|
||||||
|
function createMetamaskMiddleware ({
|
||||||
|
version,
|
||||||
|
getAccounts,
|
||||||
|
processTransaction,
|
||||||
|
processEthSignMessage,
|
||||||
|
processTypedMessage,
|
||||||
|
processPersonalMessage,
|
||||||
|
getPendingNonce,
|
||||||
|
}) {
|
||||||
|
const metamaskMiddleware = mergeMiddleware([
|
||||||
|
createScaffoldMiddleware({
|
||||||
|
// staticSubprovider
|
||||||
|
eth_syncing: false,
|
||||||
|
web3_clientVersion: `MetaMask/v${version}`,
|
||||||
|
}),
|
||||||
|
createWalletSubprovider({
|
||||||
|
getAccounts,
|
||||||
|
processTransaction,
|
||||||
|
processEthSignMessage,
|
||||||
|
processTypedMessage,
|
||||||
|
processPersonalMessage,
|
||||||
|
}),
|
||||||
|
createPendingNonceMiddleware({ getPendingNonce }),
|
||||||
|
])
|
||||||
|
return metamaskMiddleware
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPendingNonceMiddleware ({ getPendingNonce }) {
|
||||||
|
return createAsyncMiddleware(async (req, res, next) => {
|
||||||
|
if (req.method !== 'eth_getTransactionCount') return next()
|
||||||
|
const address = req.params[0]
|
||||||
|
const blockRef = req.params[1]
|
||||||
|
if (blockRef !== 'pending') return next()
|
||||||
|
req.result = await getPendingNonce(address)
|
||||||
|
})
|
||||||
|
}
|
@ -1,15 +1,17 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const EventEmitter = require('events')
|
const EventEmitter = require('events')
|
||||||
const createMetamaskProvider = require('web3-provider-engine/zero.js')
|
|
||||||
const SubproviderFromProvider = require('web3-provider-engine/subproviders/provider.js')
|
|
||||||
const createInfuraProvider = require('eth-json-rpc-infura/src/createProvider')
|
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const ComposedStore = require('obs-store/lib/composed')
|
const ComposedStore = require('obs-store/lib/composed')
|
||||||
const extend = require('xtend')
|
|
||||||
const EthQuery = require('eth-query')
|
const EthQuery = require('eth-query')
|
||||||
const createEventEmitterProxy = require('../../lib/events-proxy.js')
|
const JsonRpcEngine = require('json-rpc-engine')
|
||||||
|
const providerFromEngine = require('eth-json-rpc-middleware/providerFromEngine')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
const urlUtil = require('url')
|
const createMetamaskMiddleware = require('./createMetamaskMiddleware')
|
||||||
|
const createInfuraClient = require('./createInfuraClient')
|
||||||
|
const createJsonRpcClient = require('./createJsonRpcClient')
|
||||||
|
const createLocalhostClient = require('./createLocalhostClient')
|
||||||
|
const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy')
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ROPSTEN,
|
ROPSTEN,
|
||||||
RINKEBY,
|
RINKEBY,
|
||||||
@ -17,7 +19,6 @@ const {
|
|||||||
MAINNET,
|
MAINNET,
|
||||||
LOCALHOST,
|
LOCALHOST,
|
||||||
} = require('./enums')
|
} = require('./enums')
|
||||||
const LOCALHOST_RPC_URL = 'http://localhost:8545'
|
|
||||||
const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET]
|
const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET]
|
||||||
|
|
||||||
const env = process.env.METAMASK_ENV
|
const env = process.env.METAMASK_ENV
|
||||||
@ -39,21 +40,27 @@ module.exports = class NetworkController extends EventEmitter {
|
|||||||
this.providerStore = new ObservableStore(providerConfig)
|
this.providerStore = new ObservableStore(providerConfig)
|
||||||
this.networkStore = new ObservableStore('loading')
|
this.networkStore = new ObservableStore('loading')
|
||||||
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore })
|
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore })
|
||||||
// create event emitter proxy
|
|
||||||
this._proxy = createEventEmitterProxy()
|
|
||||||
|
|
||||||
this.on('networkDidChange', this.lookupNetwork)
|
this.on('networkDidChange', this.lookupNetwork)
|
||||||
|
// provider and block tracker
|
||||||
|
this._provider = null
|
||||||
|
this._blockTracker = null
|
||||||
|
// provider and block tracker proxies - because the network changes
|
||||||
|
this._providerProxy = null
|
||||||
|
this._blockTrackerProxy = null
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeProvider (_providerParams) {
|
initializeProvider (providerParams) {
|
||||||
this._baseProviderParams = _providerParams
|
this._baseProviderParams = providerParams
|
||||||
const { type, rpcTarget } = this.providerStore.getState()
|
const { type, rpcTarget } = this.providerStore.getState()
|
||||||
this._configureProvider({ type, rpcTarget })
|
this._configureProvider({ type, rpcTarget })
|
||||||
this._proxy.on('block', this._logBlock.bind(this))
|
|
||||||
this._proxy.on('error', this.verifyNetwork.bind(this))
|
|
||||||
this.ethQuery = new EthQuery(this._proxy)
|
|
||||||
this.lookupNetwork()
|
this.lookupNetwork()
|
||||||
return this._proxy
|
}
|
||||||
|
|
||||||
|
// return the proxies so the references will always be good
|
||||||
|
getProviderAndBlockTracker () {
|
||||||
|
const provider = this._providerProxy
|
||||||
|
const blockTracker = this._blockTrackerProxy
|
||||||
|
return { provider, blockTracker }
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyNetwork () {
|
verifyNetwork () {
|
||||||
@ -75,10 +82,11 @@ module.exports = class NetworkController extends EventEmitter {
|
|||||||
|
|
||||||
lookupNetwork () {
|
lookupNetwork () {
|
||||||
// Prevent firing when provider is not defined.
|
// Prevent firing when provider is not defined.
|
||||||
if (!this.ethQuery || !this.ethQuery.sendAsync) {
|
if (!this._provider) {
|
||||||
return log.warn('NetworkController - lookupNetwork aborted due to missing ethQuery')
|
return log.warn('NetworkController - lookupNetwork aborted due to missing provider')
|
||||||
}
|
}
|
||||||
this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
|
const ethQuery = new EthQuery(this._provider)
|
||||||
|
ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
|
||||||
if (err) return this.setNetworkState('loading')
|
if (err) return this.setNetworkState('loading')
|
||||||
log.info('web3.getNetwork returned ' + network)
|
log.info('web3.getNetwork returned ' + network)
|
||||||
this.setNetworkState(network)
|
this.setNetworkState(network)
|
||||||
@ -131,7 +139,7 @@ module.exports = class NetworkController extends EventEmitter {
|
|||||||
this._configureInfuraProvider(opts)
|
this._configureInfuraProvider(opts)
|
||||||
// other type-based rpc endpoints
|
// other type-based rpc endpoints
|
||||||
} else if (type === LOCALHOST) {
|
} else if (type === LOCALHOST) {
|
||||||
this._configureStandardProvider({ rpcUrl: LOCALHOST_RPC_URL })
|
this._configureLocalhostProvider()
|
||||||
// url-based rpc endpoints
|
// url-based rpc endpoints
|
||||||
} else if (type === 'rpc') {
|
} else if (type === 'rpc') {
|
||||||
this._configureStandardProvider({ rpcUrl: rpcTarget })
|
this._configureStandardProvider({ rpcUrl: rpcTarget })
|
||||||
@ -141,49 +149,47 @@ module.exports = class NetworkController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_configureInfuraProvider ({ type }) {
|
_configureInfuraProvider ({ type }) {
|
||||||
log.info('_configureInfuraProvider', type)
|
log.info('NetworkController - configureInfuraProvider', type)
|
||||||
const infuraProvider = createInfuraProvider({ network: type })
|
const networkClient = createInfuraClient({ network: type })
|
||||||
const infuraSubprovider = new SubproviderFromProvider(infuraProvider)
|
this._setNetworkClient(networkClient)
|
||||||
const providerParams = extend(this._baseProviderParams, {
|
}
|
||||||
engineParams: {
|
|
||||||
pollingInterval: 8000,
|
_configureLocalhostProvider () {
|
||||||
blockTrackerProvider: infuraProvider,
|
log.info('NetworkController - configureLocalhostProvider')
|
||||||
},
|
const networkClient = createLocalhostClient()
|
||||||
dataSubprovider: infuraSubprovider,
|
this._setNetworkClient(networkClient)
|
||||||
})
|
|
||||||
const provider = createMetamaskProvider(providerParams)
|
|
||||||
this._setProvider(provider)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_configureStandardProvider ({ rpcUrl }) {
|
_configureStandardProvider ({ rpcUrl }) {
|
||||||
// urlUtil handles malformed urls
|
log.info('NetworkController - configureStandardProvider', rpcUrl)
|
||||||
rpcUrl = urlUtil.parse(rpcUrl).format()
|
const networkClient = createJsonRpcClient({ rpcUrl })
|
||||||
const providerParams = extend(this._baseProviderParams, {
|
this._setNetworkClient(networkClient)
|
||||||
rpcUrl,
|
|
||||||
engineParams: {
|
|
||||||
pollingInterval: 8000,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const provider = createMetamaskProvider(providerParams)
|
|
||||||
this._setProvider(provider)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_setProvider (provider) {
|
_setNetworkClient ({ networkMiddleware, blockTracker }) {
|
||||||
// collect old block tracker events
|
const metamaskMiddleware = createMetamaskMiddleware(this._baseProviderParams)
|
||||||
const oldProvider = this._provider
|
const engine = new JsonRpcEngine()
|
||||||
let blockTrackerHandlers
|
engine.push(metamaskMiddleware)
|
||||||
if (oldProvider) {
|
engine.push(networkMiddleware)
|
||||||
// capture old block handlers
|
const provider = providerFromEngine(engine)
|
||||||
blockTrackerHandlers = oldProvider._blockTracker.proxyEventHandlers
|
this._setProviderAndBlockTracker({ provider, blockTracker })
|
||||||
// tear down
|
}
|
||||||
oldProvider.removeAllListeners()
|
|
||||||
oldProvider.stop()
|
_setProviderAndBlockTracker ({ provider, blockTracker }) {
|
||||||
|
// update or intialize proxies
|
||||||
|
if (this._providerProxy) {
|
||||||
|
this._providerProxy.setTarget(provider)
|
||||||
|
} else {
|
||||||
|
this._providerProxy = createSwappableProxy(provider)
|
||||||
}
|
}
|
||||||
// override block tracler
|
if (this._blockTrackerProxy) {
|
||||||
provider._blockTracker = createEventEmitterProxy(provider._blockTracker, blockTrackerHandlers)
|
this._blockTrackerProxy.setTarget(blockTracker)
|
||||||
// set as new provider
|
} else {
|
||||||
|
this._blockTrackerProxy = createEventEmitterProxy(blockTracker)
|
||||||
|
}
|
||||||
|
// set new provider and blockTracker
|
||||||
this._provider = provider
|
this._provider = provider
|
||||||
this._proxy.setTarget(provider)
|
this._blockTracker = blockTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
_logBlock (block) {
|
_logBlock (block) {
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
const BN = require('ethereumjs-util').BN
|
|
||||||
const EthQuery = require('eth-query')
|
const EthQuery = require('eth-query')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
|
const pify = require('pify')
|
||||||
|
|
||||||
class RecentBlocksController {
|
class RecentBlocksController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller responsible for storing, updating and managing the recent history of blocks. Blocks are back filled
|
* Controller responsible for storing, updating and managing the recent history of blocks. Blocks are back filled
|
||||||
* upon the controller's construction and then the list is updated when the given block tracker gets a 'block' event
|
* upon the controller's construction and then the list is updated when the given block tracker gets a 'latest' event
|
||||||
* (indicating that there is a new block to process).
|
* (indicating that there is a new block to process).
|
||||||
*
|
*
|
||||||
* @typedef {Object} RecentBlocksController
|
* @typedef {Object} RecentBlocksController
|
||||||
@ -16,7 +16,7 @@ class RecentBlocksController {
|
|||||||
* @param {BlockTracker} opts.blockTracker Contains objects necessary for tracking blocks and querying the blockchain
|
* @param {BlockTracker} opts.blockTracker Contains objects necessary for tracking blocks and querying the blockchain
|
||||||
* @param {BlockTracker} opts.provider The provider used to create a new EthQuery instance.
|
* @param {BlockTracker} opts.provider The provider used to create a new EthQuery instance.
|
||||||
* @property {BlockTracker} blockTracker Points to the passed BlockTracker. On RecentBlocksController construction,
|
* @property {BlockTracker} blockTracker Points to the passed BlockTracker. On RecentBlocksController construction,
|
||||||
* listens for 'block' events so that new blocks can be processed and added to storage.
|
* listens for 'latest' events so that new blocks can be processed and added to storage.
|
||||||
* @property {EthQuery} ethQuery Points to the EthQuery instance created with the passed provider
|
* @property {EthQuery} ethQuery Points to the EthQuery instance created with the passed provider
|
||||||
* @property {number} historyLength The maximum length of blocks to track
|
* @property {number} historyLength The maximum length of blocks to track
|
||||||
* @property {object} store Stores the recentBlocks
|
* @property {object} store Stores the recentBlocks
|
||||||
@ -34,7 +34,13 @@ class RecentBlocksController {
|
|||||||
}, opts.initState)
|
}, opts.initState)
|
||||||
this.store = new ObservableStore(initState)
|
this.store = new ObservableStore(initState)
|
||||||
|
|
||||||
this.blockTracker.on('block', this.processBlock.bind(this))
|
this.blockTracker.on('latest', async (newBlockNumberHex) => {
|
||||||
|
try {
|
||||||
|
await this.processBlock(newBlockNumberHex)
|
||||||
|
} catch (err) {
|
||||||
|
log.error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
this.backfill()
|
this.backfill()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +61,11 @@ class RecentBlocksController {
|
|||||||
* @param {object} newBlock The new block to modify and add to the recentBlocks array
|
* @param {object} newBlock The new block to modify and add to the recentBlocks array
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
processBlock (newBlock) {
|
async processBlock (newBlockNumberHex) {
|
||||||
|
const newBlockNumber = Number.parseInt(newBlockNumberHex, 16)
|
||||||
|
const newBlock = await this.getBlockByNumber(newBlockNumber, true)
|
||||||
|
if (!newBlock) return
|
||||||
|
|
||||||
const block = this.mapTransactionsToPrices(newBlock)
|
const block = this.mapTransactionsToPrices(newBlock)
|
||||||
|
|
||||||
const state = this.store.getState()
|
const state = this.store.getState()
|
||||||
@ -108,9 +118,9 @@ class RecentBlocksController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On this.blockTracker's first 'block' event after this RecentBlocksController's instantiation, the store.recentBlocks
|
* On this.blockTracker's first 'latest' event after this RecentBlocksController's instantiation, the store.recentBlocks
|
||||||
* array is populated with this.historyLength number of blocks. The block number of the this.blockTracker's first
|
* array is populated with this.historyLength number of blocks. The block number of the this.blockTracker's first
|
||||||
* 'block' event is used to iteratively generate all the numbers of the previous blocks, which are obtained by querying
|
* 'latest' event is used to iteratively generate all the numbers of the previous blocks, which are obtained by querying
|
||||||
* the blockchain. These blocks are backfilled so that the recentBlocks array is ordered from oldest to newest.
|
* the blockchain. These blocks are backfilled so that the recentBlocks array is ordered from oldest to newest.
|
||||||
*
|
*
|
||||||
* Each iteration over the block numbers is delayed by 100 milliseconds.
|
* Each iteration over the block numbers is delayed by 100 milliseconds.
|
||||||
@ -118,18 +128,17 @@ class RecentBlocksController {
|
|||||||
* @returns {Promise<void>} Promises undefined
|
* @returns {Promise<void>} Promises undefined
|
||||||
*/
|
*/
|
||||||
async backfill () {
|
async backfill () {
|
||||||
this.blockTracker.once('block', async (block) => {
|
this.blockTracker.once('latest', async (blockNumberHex) => {
|
||||||
const currentBlockNumber = Number.parseInt(block.number, 16)
|
const currentBlockNumber = Number.parseInt(blockNumberHex, 16)
|
||||||
const blocksToFetch = Math.min(currentBlockNumber, this.historyLength)
|
const blocksToFetch = Math.min(currentBlockNumber, this.historyLength)
|
||||||
const prevBlockNumber = currentBlockNumber - 1
|
const prevBlockNumber = currentBlockNumber - 1
|
||||||
const targetBlockNumbers = Array(blocksToFetch).fill().map((_, index) => prevBlockNumber - index)
|
const targetBlockNumbers = Array(blocksToFetch).fill().map((_, index) => prevBlockNumber - index)
|
||||||
await Promise.all(targetBlockNumbers.map(async (targetBlockNumber) => {
|
await Promise.all(targetBlockNumbers.map(async (targetBlockNumber) => {
|
||||||
try {
|
try {
|
||||||
const newBlock = await this.getBlockByNumber(targetBlockNumber)
|
const newBlock = await this.getBlockByNumber(targetBlockNumber, true)
|
||||||
|
if (!newBlock) return
|
||||||
|
|
||||||
if (newBlock) {
|
this.backfillBlock(newBlock)
|
||||||
this.backfillBlock(newBlock)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error(e)
|
log.error(e)
|
||||||
}
|
}
|
||||||
@ -137,18 +146,6 @@ class RecentBlocksController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper for this.backfill. Provides an easy way to ensure a 100 millisecond delay using await
|
|
||||||
*
|
|
||||||
* @returns {Promise<void>} Promises undefined
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
async wait () {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(resolve, 100)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses EthQuery to get a block that has a given block number.
|
* Uses EthQuery to get a block that has a given block number.
|
||||||
*
|
*
|
||||||
@ -157,13 +154,8 @@ class RecentBlocksController {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async getBlockByNumber (number) {
|
async getBlockByNumber (number) {
|
||||||
const bn = new BN(number)
|
const blockNumberHex = '0x' + number.toString(16)
|
||||||
return new Promise((resolve, reject) => {
|
return await pify(this.ethQuery.getBlockByNumber).call(this.ethQuery, blockNumberHex, true)
|
||||||
this.ethQuery.getBlockByNumber('0x' + bn.toString(16), true, (err, block) => {
|
|
||||||
if (err) reject(err)
|
|
||||||
resolve(block)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,7 @@ class TransactionController extends EventEmitter {
|
|||||||
this.store = this.txStateManager.store
|
this.store = this.txStateManager.store
|
||||||
this.nonceTracker = new NonceTracker({
|
this.nonceTracker = new NonceTracker({
|
||||||
provider: this.provider,
|
provider: this.provider,
|
||||||
|
blockTracker: this.blockTracker,
|
||||||
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
|
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
|
||||||
getConfirmedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
|
getConfirmedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
|
||||||
})
|
})
|
||||||
@ -78,13 +79,17 @@ class TransactionController extends EventEmitter {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.txStateManager.store.subscribe(() => this.emit('update:badge'))
|
this.txStateManager.store.subscribe(() => this.emit('update:badge'))
|
||||||
this._setupListners()
|
this._setupListeners()
|
||||||
// memstore is computed from a few different stores
|
// memstore is computed from a few different stores
|
||||||
this._updateMemstore()
|
this._updateMemstore()
|
||||||
this.txStateManager.store.subscribe(() => this._updateMemstore())
|
this.txStateManager.store.subscribe(() => this._updateMemstore())
|
||||||
this.networkStore.subscribe(() => this._updateMemstore())
|
this.networkStore.subscribe(() => this._updateMemstore())
|
||||||
this.preferencesStore.subscribe(() => this._updateMemstore())
|
this.preferencesStore.subscribe(() => this._updateMemstore())
|
||||||
|
|
||||||
|
// request state update to finalize initialization
|
||||||
|
this._updatePendingTxsAfterFirstBlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {number} the chainId*/
|
/** @returns {number} the chainId*/
|
||||||
getChainId () {
|
getChainId () {
|
||||||
const networkState = this.networkStore.getState()
|
const networkState = this.networkStore.getState()
|
||||||
@ -311,6 +316,11 @@ class TransactionController extends EventEmitter {
|
|||||||
this.txStateManager.setTxStatusSubmitted(txId)
|
this.txStateManager.setTxStatusSubmitted(txId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
confirmTransaction (txId) {
|
||||||
|
this.txStateManager.setTxStatusConfirmed(txId)
|
||||||
|
this._markNonceDuplicatesDropped(txId)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Convenience method for the ui thats sets the transaction to rejected
|
Convenience method for the ui thats sets the transaction to rejected
|
||||||
@param txId {number} - the tx's Id
|
@param txId {number} - the tx's Id
|
||||||
@ -354,6 +364,14 @@ class TransactionController extends EventEmitter {
|
|||||||
this.getFilteredTxList = (opts) => this.txStateManager.getFilteredTxList(opts)
|
this.getFilteredTxList = (opts) => this.txStateManager.getFilteredTxList(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// called once on startup
|
||||||
|
async _updatePendingTxsAfterFirstBlock () {
|
||||||
|
// wait for first block so we know we're ready
|
||||||
|
await this.blockTracker.getLatestBlock()
|
||||||
|
// get status update for all pending transactions (for the current network)
|
||||||
|
await this.pendingTxTracker.updatePendingTxs()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
If transaction controller was rebooted with transactions that are uncompleted
|
If transaction controller was rebooted with transactions that are uncompleted
|
||||||
in steps of the transaction signing or user confirmation process it will either
|
in steps of the transaction signing or user confirmation process it will either
|
||||||
@ -386,14 +404,14 @@ class TransactionController extends EventEmitter {
|
|||||||
is called in constructor applies the listeners for pendingTxTracker txStateManager
|
is called in constructor applies the listeners for pendingTxTracker txStateManager
|
||||||
and blockTracker
|
and blockTracker
|
||||||
*/
|
*/
|
||||||
_setupListners () {
|
_setupListeners () {
|
||||||
this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update'))
|
this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update'))
|
||||||
|
this._setupBlockTrackerListener()
|
||||||
this.pendingTxTracker.on('tx:warning', (txMeta) => {
|
this.pendingTxTracker.on('tx:warning', (txMeta) => {
|
||||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning')
|
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning')
|
||||||
})
|
})
|
||||||
this.pendingTxTracker.on('tx:confirmed', (txId) => this.txStateManager.setTxStatusConfirmed(txId))
|
|
||||||
this.pendingTxTracker.on('tx:confirmed', (txId) => this._markNonceDuplicatesDropped(txId))
|
|
||||||
this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
|
this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
|
||||||
|
this.pendingTxTracker.on('tx:confirmed', (txId) => this.confirmTransaction(txId))
|
||||||
this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
|
this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
|
||||||
if (!txMeta.firstRetryBlockNumber) {
|
if (!txMeta.firstRetryBlockNumber) {
|
||||||
txMeta.firstRetryBlockNumber = latestBlockNumber
|
txMeta.firstRetryBlockNumber = latestBlockNumber
|
||||||
@ -405,13 +423,6 @@ class TransactionController extends EventEmitter {
|
|||||||
txMeta.retryCount++
|
txMeta.retryCount++
|
||||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry')
|
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry')
|
||||||
})
|
})
|
||||||
|
|
||||||
this.blockTracker.on('block', this.pendingTxTracker.checkForTxInBlock.bind(this.pendingTxTracker))
|
|
||||||
// this is a little messy but until ethstore has been either
|
|
||||||
// removed or redone this is to guard against the race condition
|
|
||||||
this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker))
|
|
||||||
this.blockTracker.on('sync', this.pendingTxTracker.queryPendingTxs.bind(this.pendingTxTracker))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -435,6 +446,40 @@ class TransactionController extends EventEmitter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_setupBlockTrackerListener () {
|
||||||
|
let listenersAreActive = false
|
||||||
|
const latestBlockHandler = this._onLatestBlock.bind(this)
|
||||||
|
const blockTracker = this.blockTracker
|
||||||
|
const txStateManager = this.txStateManager
|
||||||
|
|
||||||
|
txStateManager.on('tx:status-update', updateSubscription)
|
||||||
|
updateSubscription()
|
||||||
|
|
||||||
|
function updateSubscription () {
|
||||||
|
const pendingTxs = txStateManager.getPendingTransactions()
|
||||||
|
if (!listenersAreActive && pendingTxs.length > 0) {
|
||||||
|
blockTracker.on('latest', latestBlockHandler)
|
||||||
|
listenersAreActive = true
|
||||||
|
} else if (listenersAreActive && !pendingTxs.length) {
|
||||||
|
blockTracker.removeListener('latest', latestBlockHandler)
|
||||||
|
listenersAreActive = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onLatestBlock (blockNumber) {
|
||||||
|
try {
|
||||||
|
await this.pendingTxTracker.updatePendingTxs()
|
||||||
|
} catch (err) {
|
||||||
|
log.error(err)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.pendingTxTracker.resubmitPendingTxs(blockNumber)
|
||||||
|
} catch (err) {
|
||||||
|
log.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Updates the memStore in transaction controller
|
Updates the memStore in transaction controller
|
||||||
*/
|
*/
|
||||||
|
@ -12,8 +12,9 @@ const Mutex = require('await-semaphore').Mutex
|
|||||||
*/
|
*/
|
||||||
class NonceTracker {
|
class NonceTracker {
|
||||||
|
|
||||||
constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) {
|
constructor ({ provider, blockTracker, getPendingTransactions, getConfirmedTransactions }) {
|
||||||
this.provider = provider
|
this.provider = provider
|
||||||
|
this.blockTracker = blockTracker
|
||||||
this.ethQuery = new EthQuery(provider)
|
this.ethQuery = new EthQuery(provider)
|
||||||
this.getPendingTransactions = getPendingTransactions
|
this.getPendingTransactions = getPendingTransactions
|
||||||
this.getConfirmedTransactions = getConfirmedTransactions
|
this.getConfirmedTransactions = getConfirmedTransactions
|
||||||
@ -34,7 +35,7 @@ class NonceTracker {
|
|||||||
* @typedef NonceDetails
|
* @typedef NonceDetails
|
||||||
* @property {number} highestLocallyConfirmed - A hex string of the highest nonce on a confirmed transaction.
|
* @property {number} highestLocallyConfirmed - A hex string of the highest nonce on a confirmed transaction.
|
||||||
* @property {number} nextNetworkNonce - The next nonce suggested by the eth_getTransactionCount method.
|
* @property {number} nextNetworkNonce - The next nonce suggested by the eth_getTransactionCount method.
|
||||||
* @property {number} highetSuggested - The maximum between the other two, the number returned.
|
* @property {number} highestSuggested - The maximum between the other two, the number returned.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,15 +81,6 @@ class NonceTracker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getCurrentBlock () {
|
|
||||||
const blockTracker = this._getBlockTracker()
|
|
||||||
const currentBlock = blockTracker.getCurrentBlock()
|
|
||||||
if (currentBlock) return currentBlock
|
|
||||||
return await new Promise((reject, resolve) => {
|
|
||||||
blockTracker.once('latest', resolve)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async _globalMutexFree () {
|
async _globalMutexFree () {
|
||||||
const globalMutex = this._lookupMutex('global')
|
const globalMutex = this._lookupMutex('global')
|
||||||
const releaseLock = await globalMutex.acquire()
|
const releaseLock = await globalMutex.acquire()
|
||||||
@ -114,9 +106,8 @@ class NonceTracker {
|
|||||||
// calculate next nonce
|
// calculate next nonce
|
||||||
// we need to make sure our base count
|
// we need to make sure our base count
|
||||||
// and pending count are from the same block
|
// and pending count are from the same block
|
||||||
const currentBlock = await this._getCurrentBlock()
|
const blockNumber = await this.blockTracker.getLatestBlock()
|
||||||
const blockNumber = currentBlock.blockNumber
|
const baseCountBN = await this.ethQuery.getTransactionCount(address, blockNumber)
|
||||||
const baseCountBN = await this.ethQuery.getTransactionCount(address, blockNumber || 'latest')
|
|
||||||
const baseCount = baseCountBN.toNumber()
|
const baseCount = baseCountBN.toNumber()
|
||||||
assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`)
|
assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`)
|
||||||
const nonceDetails = { blockNumber, baseCount }
|
const nonceDetails = { blockNumber, baseCount }
|
||||||
@ -165,15 +156,6 @@ class NonceTracker {
|
|||||||
return { name: 'local', nonce: highest, details: { startPoint, highest } }
|
return { name: 'local', nonce: highest, details: { startPoint, highest } }
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is a hotfix for the fact that the blockTracker will
|
|
||||||
// change when the network changes
|
|
||||||
|
|
||||||
/**
|
|
||||||
@returns {Object} the current blockTracker
|
|
||||||
*/
|
|
||||||
_getBlockTracker () {
|
|
||||||
return this.provider._blockTracker
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = NonceTracker
|
module.exports = NonceTracker
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const EventEmitter = require('events')
|
const EventEmitter = require('events')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
const EthQuery = require('ethjs-query')
|
const EthQuery = require('ethjs-query')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
||||||
Event emitter utility class for tracking the transactions as they<br>
|
Event emitter utility class for tracking the transactions as they<br>
|
||||||
@ -23,55 +24,26 @@ class PendingTransactionTracker extends EventEmitter {
|
|||||||
super()
|
super()
|
||||||
this.query = new EthQuery(config.provider)
|
this.query = new EthQuery(config.provider)
|
||||||
this.nonceTracker = config.nonceTracker
|
this.nonceTracker = config.nonceTracker
|
||||||
// default is one day
|
|
||||||
this.getPendingTransactions = config.getPendingTransactions
|
this.getPendingTransactions = config.getPendingTransactions
|
||||||
this.getCompletedTransactions = config.getCompletedTransactions
|
this.getCompletedTransactions = config.getCompletedTransactions
|
||||||
this.publishTransaction = config.publishTransaction
|
this.publishTransaction = config.publishTransaction
|
||||||
this._checkPendingTxs()
|
this.confirmTransaction = config.confirmTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
checks if a signed tx is in a block and
|
checks the network for signed txs and releases the nonce global lock if it is
|
||||||
if it is included emits tx status as 'confirmed'
|
|
||||||
@param block {object}, a full block
|
|
||||||
@emits tx:confirmed
|
|
||||||
@emits tx:failed
|
|
||||||
*/
|
*/
|
||||||
checkForTxInBlock (block) {
|
async updatePendingTxs () {
|
||||||
const signedTxList = this.getPendingTransactions()
|
// in order to keep the nonceTracker accurate we block it while updating pending transactions
|
||||||
if (!signedTxList.length) return
|
const nonceGlobalLock = await this.nonceTracker.getGlobalLock()
|
||||||
signedTxList.forEach((txMeta) => {
|
try {
|
||||||
const txHash = txMeta.hash
|
const pendingTxs = this.getPendingTransactions()
|
||||||
const txId = txMeta.id
|
await Promise.all(pendingTxs.map((txMeta) => this._checkPendingTx(txMeta)))
|
||||||
|
} catch (err) {
|
||||||
if (!txHash) {
|
log.error('PendingTransactionTracker - Error updating pending transactions')
|
||||||
const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.')
|
log.error(err)
|
||||||
noTxHashErr.name = 'NoTxHashError'
|
|
||||||
this.emit('tx:failed', txId, noTxHashErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
block.transactions.forEach((tx) => {
|
|
||||||
if (tx.hash === txHash) this.emit('tx:confirmed', txId)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
asks the network for the transaction to see if a block number is included on it
|
|
||||||
if we have skipped/missed blocks
|
|
||||||
@param object - oldBlock newBlock
|
|
||||||
*/
|
|
||||||
queryPendingTxs ({ oldBlock, newBlock }) {
|
|
||||||
// check pending transactions on start
|
|
||||||
if (!oldBlock) {
|
|
||||||
this._checkPendingTxs()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
// if we synced by more than one block, check for missed pending transactions
|
nonceGlobalLock.releaseLock()
|
||||||
const diff = Number.parseInt(newBlock.number, 16) - Number.parseInt(oldBlock.number, 16)
|
|
||||||
if (diff > 1) this._checkPendingTxs()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,11 +51,11 @@ class PendingTransactionTracker extends EventEmitter {
|
|||||||
@param block {object} - a block object
|
@param block {object} - a block object
|
||||||
@emits tx:warning
|
@emits tx:warning
|
||||||
*/
|
*/
|
||||||
resubmitPendingTxs (block) {
|
resubmitPendingTxs (blockNumber) {
|
||||||
const pending = this.getPendingTransactions()
|
const pending = this.getPendingTransactions()
|
||||||
// only try resubmitting if their are transactions to resubmit
|
// only try resubmitting if their are transactions to resubmit
|
||||||
if (!pending.length) return
|
if (!pending.length) return
|
||||||
pending.forEach((txMeta) => this._resubmitTx(txMeta, block.number).catch((err) => {
|
pending.forEach((txMeta) => this._resubmitTx(txMeta, blockNumber).catch((err) => {
|
||||||
/*
|
/*
|
||||||
Dont marked as failed if the error is a "known" transaction warning
|
Dont marked as failed if the error is a "known" transaction warning
|
||||||
"there is already a transaction with the same sender-nonce
|
"there is already a transaction with the same sender-nonce
|
||||||
@ -145,6 +117,7 @@ class PendingTransactionTracker extends EventEmitter {
|
|||||||
this.emit('tx:retry', txMeta)
|
this.emit('tx:retry', txMeta)
|
||||||
return txHash
|
return txHash
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Ask the network for the transaction to see if it has been include in a block
|
Ask the network for the transaction to see if it has been include in a block
|
||||||
@param txMeta {Object} - the txMeta object
|
@param txMeta {Object} - the txMeta object
|
||||||
@ -174,9 +147,8 @@ class PendingTransactionTracker extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get latest transaction status
|
// get latest transaction status
|
||||||
let txParams
|
|
||||||
try {
|
try {
|
||||||
txParams = await this.query.getTransactionByHash(txHash)
|
const txParams = await this.query.getTransactionByHash(txHash)
|
||||||
if (!txParams) return
|
if (!txParams) return
|
||||||
if (txParams.blockNumber) {
|
if (txParams.blockNumber) {
|
||||||
this.emit('tx:confirmed', txId)
|
this.emit('tx:confirmed', txId)
|
||||||
@ -190,27 +162,13 @@ class PendingTransactionTracker extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
checks the network for signed txs and releases the nonce global lock if it is
|
|
||||||
*/
|
|
||||||
async _checkPendingTxs () {
|
|
||||||
const signedTxList = this.getPendingTransactions()
|
|
||||||
// in order to keep the nonceTracker accurate we block it while updating pending transactions
|
|
||||||
const { releaseLock } = await this.nonceTracker.getGlobalLock()
|
|
||||||
try {
|
|
||||||
await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta)))
|
|
||||||
} catch (err) {
|
|
||||||
log.error('PendingTransactionWatcher - Error updating pending transactions')
|
|
||||||
log.error(err)
|
|
||||||
}
|
|
||||||
releaseLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
checks to see if a confirmed txMeta has the same nonce
|
checks to see if a confirmed txMeta has the same nonce
|
||||||
@param txMeta {Object} - txMeta object
|
@param txMeta {Object} - txMeta object
|
||||||
@returns {boolean}
|
@returns {boolean}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
async _checkIfNonceIsTaken (txMeta) {
|
async _checkIfNonceIsTaken (txMeta) {
|
||||||
const address = txMeta.txParams.from
|
const address = txMeta.txParams.from
|
||||||
const completed = this.getCompletedTransactions(address)
|
const completed = this.getCompletedTransactions(address)
|
||||||
|
@ -25,7 +25,7 @@ class TxGasUtil {
|
|||||||
@returns {object} the txMeta object with the gas written to the txParams
|
@returns {object} the txMeta object with the gas written to the txParams
|
||||||
*/
|
*/
|
||||||
async analyzeGasUsage (txMeta) {
|
async analyzeGasUsage (txMeta) {
|
||||||
const block = await this.query.getBlockByNumber('latest', true)
|
const block = await this.query.getBlockByNumber('latest', false)
|
||||||
let estimatedGasHex
|
let estimatedGasHex
|
||||||
try {
|
try {
|
||||||
estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit)
|
estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit)
|
||||||
|
@ -7,14 +7,13 @@
|
|||||||
* on each new block.
|
* on each new block.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const async = require('async')
|
|
||||||
const EthQuery = require('eth-query')
|
const EthQuery = require('eth-query')
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const EventEmitter = require('events').EventEmitter
|
const log = require('loglevel')
|
||||||
function noop () {}
|
const pify = require('pify')
|
||||||
|
|
||||||
|
|
||||||
class AccountTracker extends EventEmitter {
|
class AccountTracker {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This module is responsible for tracking any number of accounts and caching their current balances & transaction
|
* This module is responsible for tracking any number of accounts and caching their current balances & transaction
|
||||||
@ -35,8 +34,6 @@ class AccountTracker extends EventEmitter {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
constructor (opts = {}) {
|
constructor (opts = {}) {
|
||||||
super()
|
|
||||||
|
|
||||||
const initState = {
|
const initState = {
|
||||||
accounts: {},
|
accounts: {},
|
||||||
currentBlockGasLimit: '',
|
currentBlockGasLimit: '',
|
||||||
@ -44,12 +41,12 @@ class AccountTracker extends EventEmitter {
|
|||||||
this.store = new ObservableStore(initState)
|
this.store = new ObservableStore(initState)
|
||||||
|
|
||||||
this._provider = opts.provider
|
this._provider = opts.provider
|
||||||
this._query = new EthQuery(this._provider)
|
this._query = pify(new EthQuery(this._provider))
|
||||||
this._blockTracker = opts.blockTracker
|
this._blockTracker = opts.blockTracker
|
||||||
// subscribe to latest block
|
// subscribe to latest block
|
||||||
this._blockTracker.on('block', this._updateForBlock.bind(this))
|
this._blockTracker.on('latest', this._updateForBlock.bind(this))
|
||||||
// blockTracker.currentBlock may be null
|
// blockTracker.currentBlock may be null
|
||||||
this._currentBlockNumber = this._blockTracker.currentBlock
|
this._currentBlockNumber = this._blockTracker.getCurrentBlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,49 +64,57 @@ class AccountTracker extends EventEmitter {
|
|||||||
const accounts = this.store.getState().accounts
|
const accounts = this.store.getState().accounts
|
||||||
const locals = Object.keys(accounts)
|
const locals = Object.keys(accounts)
|
||||||
|
|
||||||
const toAdd = []
|
const accountsToAdd = []
|
||||||
addresses.forEach((upstream) => {
|
addresses.forEach((upstream) => {
|
||||||
if (!locals.includes(upstream)) {
|
if (!locals.includes(upstream)) {
|
||||||
toAdd.push(upstream)
|
accountsToAdd.push(upstream)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const toRemove = []
|
const accountsToRemove = []
|
||||||
locals.forEach((local) => {
|
locals.forEach((local) => {
|
||||||
if (!addresses.includes(local)) {
|
if (!addresses.includes(local)) {
|
||||||
toRemove.push(local)
|
accountsToRemove.push(local)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
toAdd.forEach(upstream => this.addAccount(upstream))
|
this.addAccounts(accountsToAdd)
|
||||||
toRemove.forEach(local => this.removeAccount(local))
|
this.removeAccount(accountsToRemove)
|
||||||
this._updateAccounts()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new address to this AccountTracker's accounts object, which points to an empty object. This object will be
|
* Adds new addresses to track the balances of
|
||||||
* given a balance as long this._currentBlockNumber is defined.
|
* given a balance as long this._currentBlockNumber is defined.
|
||||||
*
|
*
|
||||||
* @param {string} address A hex address of a new account to store in this AccountTracker's accounts object
|
* @param {array} addresses An array of hex addresses of new accounts to track
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
addAccount (address) {
|
addAccounts (addresses) {
|
||||||
const accounts = this.store.getState().accounts
|
const accounts = this.store.getState().accounts
|
||||||
accounts[address] = {}
|
// add initial state for addresses
|
||||||
|
addresses.forEach(address => {
|
||||||
|
accounts[address] = {}
|
||||||
|
})
|
||||||
|
// save accounts state
|
||||||
this.store.updateState({ accounts })
|
this.store.updateState({ accounts })
|
||||||
|
// fetch balances for the accounts if there is block number ready
|
||||||
if (!this._currentBlockNumber) return
|
if (!this._currentBlockNumber) return
|
||||||
this._updateAccount(address)
|
addresses.forEach(address => this._updateAccount(address))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes an account from this AccountTracker's accounts object
|
* Removes accounts from being tracked
|
||||||
*
|
*
|
||||||
* @param {string} address A hex address of a the account to remove
|
* @param {array} an array of hex addresses to stop tracking
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
removeAccount (address) {
|
removeAccount (addresses) {
|
||||||
const accounts = this.store.getState().accounts
|
const accounts = this.store.getState().accounts
|
||||||
delete accounts[address]
|
// remove each state object
|
||||||
|
addresses.forEach(address => {
|
||||||
|
delete accounts[address]
|
||||||
|
})
|
||||||
|
// save accounts state
|
||||||
this.store.updateState({ accounts })
|
this.store.updateState({ accounts })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,71 +123,56 @@ class AccountTracker extends EventEmitter {
|
|||||||
* via EthQuery
|
* via EthQuery
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {object} block Data about the block that contains the data to update to.
|
* @param {number} blockNumber the block number to update to.
|
||||||
* @fires 'block' The updated state, if all account updates are successful
|
* @fires 'block' The updated state, if all account updates are successful
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_updateForBlock (block) {
|
async _updateForBlock (blockNumber) {
|
||||||
this._currentBlockNumber = block.number
|
this._currentBlockNumber = blockNumber
|
||||||
const currentBlockGasLimit = block.gasLimit
|
|
||||||
|
|
||||||
|
// block gasLimit polling shouldn't be in account-tracker shouldn't be here...
|
||||||
|
const currentBlock = await this._query.getBlockByNumber(blockNumber, false)
|
||||||
|
if (!currentBlock) return
|
||||||
|
const currentBlockGasLimit = currentBlock.gasLimit
|
||||||
this.store.updateState({ currentBlockGasLimit })
|
this.store.updateState({ currentBlockGasLimit })
|
||||||
|
|
||||||
async.parallel([
|
try {
|
||||||
this._updateAccounts.bind(this),
|
await this._updateAccounts()
|
||||||
], (err) => {
|
} catch (err) {
|
||||||
if (err) return console.error(err)
|
log.error(err)
|
||||||
this.emit('block', this.store.getState())
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls this._updateAccount for each account in this.store
|
* Calls this._updateAccount for each account in this.store
|
||||||
*
|
*
|
||||||
* @param {Function} cb A callback to pass to this._updateAccount, called after each account is successfully updated
|
* @returns {Promise} after all account balances updated
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_updateAccounts (cb = noop) {
|
async _updateAccounts () {
|
||||||
const accounts = this.store.getState().accounts
|
const accounts = this.store.getState().accounts
|
||||||
const addresses = Object.keys(accounts)
|
const addresses = Object.keys(accounts)
|
||||||
async.each(addresses, this._updateAccount.bind(this), cb)
|
await Promise.all(addresses.map(this._updateAccount.bind(this)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the current balance of an account. Gets an updated balance via this._getAccount.
|
* Updates the current balance of an account.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {string} address A hex address of a the account to be updated
|
* @param {string} address A hex address of a the account to be updated
|
||||||
* @param {Function} cb A callback to call once the account at address is successfully update
|
* @returns {Promise} after the account balance is updated
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_updateAccount (address, cb = noop) {
|
async _updateAccount (address) {
|
||||||
this._getAccount(address, (err, result) => {
|
// query balance
|
||||||
if (err) return cb(err)
|
const balance = await this._query.getBalance(address)
|
||||||
result.address = address
|
const result = { address, balance }
|
||||||
const accounts = this.store.getState().accounts
|
// update accounts state
|
||||||
// only populate if the entry is still present
|
const { accounts } = this.store.getState()
|
||||||
if (accounts[address]) {
|
// only populate if the entry is still present
|
||||||
accounts[address] = result
|
if (!accounts[address]) return
|
||||||
this.store.updateState({ accounts })
|
accounts[address] = result
|
||||||
}
|
this.store.updateState({ accounts })
|
||||||
cb(null, result)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current balance of an account via EthQuery.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {string} address A hex address of a the account to query
|
|
||||||
* @param {Function} cb A callback to call once the account at address is successfully update
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
_getAccount (address, cb = noop) {
|
|
||||||
const query = this._query
|
|
||||||
async.parallel({
|
|
||||||
balance: query.getBalance.bind(query, address),
|
|
||||||
}, cb)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,19 @@ const ENVIRONMENT_TYPE_POPUP = 'popup'
|
|||||||
const ENVIRONMENT_TYPE_NOTIFICATION = 'notification'
|
const ENVIRONMENT_TYPE_NOTIFICATION = 'notification'
|
||||||
const ENVIRONMENT_TYPE_FULLSCREEN = 'fullscreen'
|
const ENVIRONMENT_TYPE_FULLSCREEN = 'fullscreen'
|
||||||
|
|
||||||
|
const PLATFORM_BRAVE = 'Brave'
|
||||||
|
const PLATFORM_CHROME = 'Chrome'
|
||||||
|
const PLATFORM_EDGE = 'Edge'
|
||||||
|
const PLATFORM_FIREFOX = 'Firefox'
|
||||||
|
const PLATFORM_OPERA = 'Opera'
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ENVIRONMENT_TYPE_POPUP,
|
ENVIRONMENT_TYPE_POPUP,
|
||||||
ENVIRONMENT_TYPE_NOTIFICATION,
|
ENVIRONMENT_TYPE_NOTIFICATION,
|
||||||
ENVIRONMENT_TYPE_FULLSCREEN,
|
ENVIRONMENT_TYPE_FULLSCREEN,
|
||||||
|
PLATFORM_BRAVE,
|
||||||
|
PLATFORM_CHROME,
|
||||||
|
PLATFORM_EDGE,
|
||||||
|
PLATFORM_FIREFOX,
|
||||||
|
PLATFORM_OPERA,
|
||||||
}
|
}
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
/**
|
|
||||||
* Returns an EventEmitter that proxies events from the given event emitter
|
|
||||||
* @param {any} eventEmitter
|
|
||||||
* @param {object} listeners - The listeners to proxy to
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
module.exports = function createEventEmitterProxy (eventEmitter, listeners) {
|
|
||||||
let target = eventEmitter
|
|
||||||
const eventHandlers = listeners || {}
|
|
||||||
const proxy = /** @type {any} */ (new Proxy({}, {
|
|
||||||
get: (_, name) => {
|
|
||||||
// intercept listeners
|
|
||||||
if (name === 'on') return addListener
|
|
||||||
if (name === 'setTarget') return setTarget
|
|
||||||
if (name === 'proxyEventHandlers') return eventHandlers
|
|
||||||
return (/** @type {any} */ (target))[name]
|
|
||||||
},
|
|
||||||
set: (_, name, value) => {
|
|
||||||
target[name] = value
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
function setTarget (/** @type {EventEmitter} */ eventEmitter) {
|
|
||||||
target = eventEmitter
|
|
||||||
// migrate listeners
|
|
||||||
Object.keys(eventHandlers).forEach((name) => {
|
|
||||||
/** @type {Array<Function>} */ (eventHandlers[name]).forEach((handler) => target.on(name, handler))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Attaches a function to be called whenever the specified event is emitted
|
|
||||||
* @param {string} name
|
|
||||||
* @param {Function} handler
|
|
||||||
*/
|
|
||||||
function addListener (name, handler) {
|
|
||||||
if (!eventHandlers[name]) eventHandlers[name] = []
|
|
||||||
eventHandlers[name].push(handler)
|
|
||||||
target.on(name, handler)
|
|
||||||
}
|
|
||||||
if (listeners) proxy.setTarget(eventEmitter)
|
|
||||||
return proxy
|
|
||||||
}
|
|
@ -69,10 +69,39 @@ module.exports = class MessageManager extends EventEmitter {
|
|||||||
* new Message to this.messages, and to save the unapproved Messages from that list to this.memStore.
|
* new Message to this.messages, and to save the unapproved Messages from that list to this.memStore.
|
||||||
*
|
*
|
||||||
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
||||||
|
* @param {Object} req (optional) The original request object possibly containing the origin
|
||||||
|
* @returns {promise} after signature has been
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
addUnapprovedMessageAsync (msgParams, req) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const msgId = this.addUnapprovedMessage(msgParams, req)
|
||||||
|
// await finished
|
||||||
|
this.once(`${msgId}:finished`, (data) => {
|
||||||
|
switch (data.status) {
|
||||||
|
case 'signed':
|
||||||
|
return resolve(data.rawSig)
|
||||||
|
case 'rejected':
|
||||||
|
return reject(new Error('MetaMask Message Signature: User denied message signature.'))
|
||||||
|
default:
|
||||||
|
return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Message with an 'unapproved' status using the passed msgParams. this.addMsg is called to add the
|
||||||
|
* new Message to this.messages, and to save the unapproved Messages from that list to this.memStore.
|
||||||
|
*
|
||||||
|
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
||||||
|
* @param {Object} req (optional) The original request object where the origin may be specificied
|
||||||
* @returns {number} The id of the newly created message.
|
* @returns {number} The id of the newly created message.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
addUnapprovedMessage (msgParams) {
|
addUnapprovedMessage (msgParams, req) {
|
||||||
|
// add origin from request
|
||||||
|
if (req) msgParams.origin = req.origin
|
||||||
msgParams.data = normalizeMsgData(msgParams.data)
|
msgParams.data = normalizeMsgData(msgParams.data)
|
||||||
// create txData obj with parameters and meta data
|
// create txData obj with parameters and meta data
|
||||||
var time = (new Date()).getTime()
|
var time = (new Date()).getTime()
|
||||||
|
@ -73,11 +73,43 @@ module.exports = class PersonalMessageManager extends EventEmitter {
|
|||||||
* this.memStore.
|
* this.memStore.
|
||||||
*
|
*
|
||||||
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
||||||
|
* @param {Object} req (optional) The original request object possibly containing the origin
|
||||||
|
* @returns {promise} When the message has been signed or rejected
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
addUnapprovedMessageAsync (msgParams, req) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!msgParams.from) {
|
||||||
|
reject(new Error('MetaMask Message Signature: from field is required.'))
|
||||||
|
}
|
||||||
|
const msgId = this.addUnapprovedMessage(msgParams, req)
|
||||||
|
this.once(`${msgId}:finished`, (data) => {
|
||||||
|
switch (data.status) {
|
||||||
|
case 'signed':
|
||||||
|
return resolve(data.rawSig)
|
||||||
|
case 'rejected':
|
||||||
|
return reject(new Error('MetaMask Message Signature: User denied message signature.'))
|
||||||
|
default:
|
||||||
|
return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new PersonalMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
|
||||||
|
* the new PersonalMessage to this.messages, and to save the unapproved PersonalMessages from that list to
|
||||||
|
* this.memStore.
|
||||||
|
*
|
||||||
|
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
||||||
|
* @param {Object} req (optional) The original request object possibly containing the origin
|
||||||
* @returns {number} The id of the newly created PersonalMessage.
|
* @returns {number} The id of the newly created PersonalMessage.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
addUnapprovedMessage (msgParams) {
|
addUnapprovedMessage (msgParams, req) {
|
||||||
log.debug(`PersonalMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
|
log.debug(`PersonalMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
|
||||||
|
// add origin from request
|
||||||
|
if (req) msgParams.origin = req.origin
|
||||||
msgParams.data = this.normalizeMsgData(msgParams.data)
|
msgParams.data = this.normalizeMsgData(msgParams.data)
|
||||||
// create txData obj with parameters and meta data
|
// create txData obj with parameters and meta data
|
||||||
var time = (new Date()).getTime()
|
var time = (new Date()).getTime()
|
||||||
@ -257,4 +289,3 @@ module.exports = class PersonalMessageManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ PortDuplexStream.prototype._onMessage = function (msg) {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
PortDuplexStream.prototype._onDisconnect = function () {
|
PortDuplexStream.prototype._onDisconnect = function () {
|
||||||
this.destroy()
|
this.destroy && this.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -70,11 +70,11 @@ function simplifyErrorMessages (report) {
|
|||||||
|
|
||||||
function rewriteErrorMessages (report, rewriteFn) {
|
function rewriteErrorMessages (report, rewriteFn) {
|
||||||
// rewrite top level message
|
// rewrite top level message
|
||||||
if (report.message) report.message = rewriteFn(report.message)
|
if (typeof report.message === 'string') report.message = rewriteFn(report.message)
|
||||||
// rewrite each exception message
|
// rewrite each exception message
|
||||||
if (report.exception && report.exception.values) {
|
if (report.exception && report.exception.values) {
|
||||||
report.exception.values.forEach(item => {
|
report.exception.values.forEach(item => {
|
||||||
item.value = rewriteFn(item.value)
|
if (typeof item.value === 'string') item.value = rewriteFn(item.value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,11 +72,40 @@ module.exports = class TypedMessageManager extends EventEmitter {
|
|||||||
* this.memStore. Before any of this is done, msgParams are validated
|
* this.memStore. Before any of this is done, msgParams are validated
|
||||||
*
|
*
|
||||||
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
||||||
|
* @param {Object} req (optional) The original request object possibly containing the origin
|
||||||
|
* @returns {promise} When the message has been signed or rejected
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
addUnapprovedMessageAsync (msgParams, req) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const msgId = this.addUnapprovedMessage(msgParams, req)
|
||||||
|
this.once(`${msgId}:finished`, (data) => {
|
||||||
|
switch (data.status) {
|
||||||
|
case 'signed':
|
||||||
|
return resolve(data.rawSig)
|
||||||
|
case 'rejected':
|
||||||
|
return reject(new Error('MetaMask Message Signature: User denied message signature.'))
|
||||||
|
default:
|
||||||
|
return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new TypedMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
|
||||||
|
* the new TypedMessage to this.messages, and to save the unapproved TypedMessages from that list to
|
||||||
|
* this.memStore. Before any of this is done, msgParams are validated
|
||||||
|
*
|
||||||
|
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
||||||
|
* @param {Object} req (optional) The original request object possibly containing the origin
|
||||||
* @returns {number} The id of the newly created TypedMessage.
|
* @returns {number} The id of the newly created TypedMessage.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
addUnapprovedMessage (msgParams) {
|
addUnapprovedMessage (msgParams, req) {
|
||||||
this.validateParams(msgParams)
|
this.validateParams(msgParams)
|
||||||
|
// add origin from request
|
||||||
|
if (req) msgParams.origin = req.origin
|
||||||
|
|
||||||
log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
|
log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
|
||||||
// create txData obj with parameters and meta data
|
// create txData obj with parameters and meta data
|
||||||
|
@ -5,6 +5,11 @@ const {
|
|||||||
ENVIRONMENT_TYPE_POPUP,
|
ENVIRONMENT_TYPE_POPUP,
|
||||||
ENVIRONMENT_TYPE_NOTIFICATION,
|
ENVIRONMENT_TYPE_NOTIFICATION,
|
||||||
ENVIRONMENT_TYPE_FULLSCREEN,
|
ENVIRONMENT_TYPE_FULLSCREEN,
|
||||||
|
PLATFORM_FIREFOX,
|
||||||
|
PLATFORM_OPERA,
|
||||||
|
PLATFORM_CHROME,
|
||||||
|
PLATFORM_EDGE,
|
||||||
|
PLATFORM_BRAVE,
|
||||||
} = require('./enums')
|
} = require('./enums')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,6 +42,29 @@ const getEnvironmentType = (url = window.location.href) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the platform (browser) where the extension is running.
|
||||||
|
*
|
||||||
|
* @returns {string} the platform ENUM
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const getPlatform = _ => {
|
||||||
|
const ua = navigator.userAgent
|
||||||
|
if (ua.search('Firefox') !== -1) {
|
||||||
|
return PLATFORM_FIREFOX
|
||||||
|
} else {
|
||||||
|
if (window && window.chrome && window.chrome.ipcRenderer) {
|
||||||
|
return PLATFORM_BRAVE
|
||||||
|
} else if (ua.search('Edge') !== -1) {
|
||||||
|
return PLATFORM_EDGE
|
||||||
|
} else if (ua.search('OPR') !== -1) {
|
||||||
|
return PLATFORM_OPERA
|
||||||
|
} else {
|
||||||
|
return PLATFORM_CHROME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a given balance of ETH, represented as a hex string, is sufficient to pay a value plus a gas fee
|
* Checks whether a given balance of ETH, represented as a hex string, is sufficient to pay a value plus a gas fee
|
||||||
*
|
*
|
||||||
@ -99,7 +127,22 @@ function BnMultiplyByFraction (targetBN, numerator, denominator) {
|
|||||||
return targetBN.mul(numBN).div(denomBN)
|
return targetBN.mul(numBN).div(denomBN)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyListeners (listeners, emitter) {
|
||||||
|
Object.keys(listeners).forEach((key) => {
|
||||||
|
emitter.on(key, listeners[key])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeListeners (listeners, emitter) {
|
||||||
|
Object.keys(listeners).forEach((key) => {
|
||||||
|
emitter.removeListener(key, listeners[key])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
removeListeners,
|
||||||
|
applyListeners,
|
||||||
|
getPlatform,
|
||||||
getStack,
|
getStack,
|
||||||
getEnvironmentType,
|
getEnvironmentType,
|
||||||
sufficientBalance,
|
sufficientBalance,
|
||||||
|
@ -46,9 +46,10 @@ const BN = require('ethereumjs-util').BN
|
|||||||
const GWEI_BN = new BN('1000000000')
|
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 log = require('loglevel')
|
const log = require('loglevel')
|
||||||
const TrezorKeyring = require('eth-trezor-keyring')
|
const TrezorKeyring = require('eth-trezor-keyring')
|
||||||
|
const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring')
|
||||||
|
const EthQuery = require('eth-query')
|
||||||
|
|
||||||
module.exports = class MetamaskController extends EventEmitter {
|
module.exports = class MetamaskController extends EventEmitter {
|
||||||
|
|
||||||
@ -107,8 +108,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
this.blacklistController.scheduleUpdates()
|
this.blacklistController.scheduleUpdates()
|
||||||
|
|
||||||
// rpc provider
|
// rpc provider
|
||||||
this.provider = this.initializeProvider()
|
this.initializeProvider()
|
||||||
this.blockTracker = this.provider._blockTracker
|
this.provider = this.networkController.getProviderAndBlockTracker().provider
|
||||||
|
this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker
|
||||||
|
|
||||||
// token exchange rate tracker
|
// token exchange rate tracker
|
||||||
this.tokenRatesController = new TokenRatesController({
|
this.tokenRatesController = new TokenRatesController({
|
||||||
@ -127,7 +129,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// key mgmt
|
// key mgmt
|
||||||
const additionalKeyrings = [TrezorKeyring]
|
const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring]
|
||||||
this.keyringController = new KeyringController({
|
this.keyringController = new KeyringController({
|
||||||
keyringTypes: additionalKeyrings,
|
keyringTypes: additionalKeyrings,
|
||||||
initState: initState.KeyringController,
|
initState: initState.KeyringController,
|
||||||
@ -252,28 +254,22 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
static: {
|
static: {
|
||||||
eth_syncing: false,
|
eth_syncing: false,
|
||||||
web3_clientVersion: `MetaMask/v${version}`,
|
web3_clientVersion: `MetaMask/v${version}`,
|
||||||
eth_sendTransaction: (payload, next, end) => {
|
|
||||||
const origin = payload.origin
|
|
||||||
const txParams = payload.params[0]
|
|
||||||
nodeify(this.txController.newUnapprovedTransaction, this.txController)(txParams, { origin }, end)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// account mgmt
|
// account mgmt
|
||||||
getAccounts: (cb) => {
|
getAccounts: async () => {
|
||||||
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
||||||
const result = []
|
|
||||||
const selectedAddress = this.preferencesController.getSelectedAddress()
|
const selectedAddress = this.preferencesController.getSelectedAddress()
|
||||||
|
|
||||||
// only show address if account is unlocked
|
// only show address if account is unlocked
|
||||||
if (isUnlocked && selectedAddress) {
|
if (isUnlocked && selectedAddress) {
|
||||||
result.push(selectedAddress)
|
return [selectedAddress]
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
}
|
}
|
||||||
cb(null, result)
|
|
||||||
},
|
},
|
||||||
// tx signing
|
// tx signing
|
||||||
// old style msg signing
|
processTransaction: this.newUnapprovedTransaction.bind(this),
|
||||||
processMessage: this.newUnsignedMessage.bind(this),
|
// msg signing
|
||||||
// personal_sign msg signing
|
processEthSignMessage: this.newUnsignedMessage.bind(this),
|
||||||
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
|
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
|
||||||
processTypedMessage: this.newUnsignedTypedMessage.bind(this),
|
processTypedMessage: this.newUnsignedTypedMessage.bind(this),
|
||||||
}
|
}
|
||||||
@ -377,9 +373,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
connectHardware: nodeify(this.connectHardware, this),
|
connectHardware: nodeify(this.connectHardware, this),
|
||||||
forgetDevice: nodeify(this.forgetDevice, this),
|
forgetDevice: nodeify(this.forgetDevice, this),
|
||||||
checkHardwareStatus: nodeify(this.checkHardwareStatus, this),
|
checkHardwareStatus: nodeify(this.checkHardwareStatus, this),
|
||||||
|
unlockHardwareWalletAccount: nodeify(this.unlockHardwareWalletAccount, this),
|
||||||
// TREZOR
|
|
||||||
unlockTrezorAccount: nodeify(this.unlockTrezorAccount, this),
|
|
||||||
|
|
||||||
// vault management
|
// vault management
|
||||||
submitPassword: nodeify(this.submitPassword, this),
|
submitPassword: nodeify(this.submitPassword, this),
|
||||||
@ -481,12 +475,32 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
async createNewVaultAndRestore (password, seed) {
|
async createNewVaultAndRestore (password, seed) {
|
||||||
const releaseLock = await this.createVaultMutex.acquire()
|
const releaseLock = await this.createVaultMutex.acquire()
|
||||||
try {
|
try {
|
||||||
|
let accounts, lastBalance
|
||||||
|
|
||||||
|
const keyringController = this.keyringController
|
||||||
|
|
||||||
// clear known identities
|
// clear known identities
|
||||||
this.preferencesController.setAddresses([])
|
this.preferencesController.setAddresses([])
|
||||||
// create new vault
|
// create new vault
|
||||||
const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
|
const vault = await keyringController.createNewVaultAndRestore(password, seed)
|
||||||
|
|
||||||
|
const ethQuery = new EthQuery(this.provider)
|
||||||
|
accounts = await keyringController.getAccounts()
|
||||||
|
lastBalance = await this.getBalance(accounts[accounts.length - 1], ethQuery)
|
||||||
|
|
||||||
|
const primaryKeyring = keyringController.getKeyringsByType('HD Key Tree')[0]
|
||||||
|
if (!primaryKeyring) {
|
||||||
|
throw new Error('MetamaskController - No HD Key Tree found')
|
||||||
|
}
|
||||||
|
|
||||||
|
// seek out the first zero balance
|
||||||
|
while (lastBalance !== '0x0') {
|
||||||
|
await keyringController.addNewAccount(primaryKeyring)
|
||||||
|
accounts = await keyringController.getAccounts()
|
||||||
|
lastBalance = await this.getBalance(accounts[accounts.length - 1], ethQuery)
|
||||||
|
}
|
||||||
|
|
||||||
// set new identities
|
// set new identities
|
||||||
const accounts = await this.keyringController.getAccounts()
|
|
||||||
this.preferencesController.setAddresses(accounts)
|
this.preferencesController.setAddresses(accounts)
|
||||||
this.selectFirstIdentity()
|
this.selectFirstIdentity()
|
||||||
releaseLock()
|
releaseLock()
|
||||||
@ -497,6 +511,30 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an account balance from the AccountTracker or request it directly from the network.
|
||||||
|
* @param {string} address - The account address
|
||||||
|
* @param {EthQuery} ethQuery - The EthQuery instance to use when asking the network
|
||||||
|
*/
|
||||||
|
getBalance (address, ethQuery) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const cached = this.accountTracker.store.getState().accounts[address]
|
||||||
|
|
||||||
|
if (cached && cached.balance) {
|
||||||
|
resolve(cached.balance)
|
||||||
|
} else {
|
||||||
|
ethQuery.getBalance(address, (error, balance) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
log.error(error)
|
||||||
|
} else {
|
||||||
|
resolve(balance || '0x0')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Submits the user's password and attempts to unlock the vault.
|
* Submits the user's password and attempts to unlock the vault.
|
||||||
* Also synchronizes the preferencesController, to ensure its schema
|
* Also synchronizes the preferencesController, to ensure its schema
|
||||||
@ -540,45 +578,57 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
// Hardware
|
// Hardware
|
||||||
//
|
//
|
||||||
|
|
||||||
|
async getKeyringForDevice (deviceName, hdPath = null) {
|
||||||
|
let keyringName = null
|
||||||
|
switch (deviceName) {
|
||||||
|
case 'trezor':
|
||||||
|
keyringName = TrezorKeyring.type
|
||||||
|
break
|
||||||
|
case 'ledger':
|
||||||
|
keyringName = LedgerBridgeKeyring.type
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error('MetamaskController:getKeyringForDevice - Unknown device')
|
||||||
|
}
|
||||||
|
let keyring = await this.keyringController.getKeyringsByType(keyringName)[0]
|
||||||
|
if (!keyring) {
|
||||||
|
keyring = await this.keyringController.addNewKeyring(keyringName)
|
||||||
|
}
|
||||||
|
if (hdPath && keyring.setHdPath) {
|
||||||
|
keyring.setHdPath(hdPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyring.network = this.networkController.getProviderConfig().type
|
||||||
|
|
||||||
|
return keyring
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch account list from a trezor device.
|
* Fetch account list from a trezor device.
|
||||||
*
|
*
|
||||||
* @returns [] accounts
|
* @returns [] accounts
|
||||||
*/
|
*/
|
||||||
async connectHardware (deviceName, page) {
|
async connectHardware (deviceName, page, hdPath) {
|
||||||
|
const keyring = await this.getKeyringForDevice(deviceName, hdPath)
|
||||||
switch (deviceName) {
|
let accounts = []
|
||||||
case 'trezor':
|
switch (page) {
|
||||||
const keyringController = this.keyringController
|
case -1:
|
||||||
const oldAccounts = await keyringController.getAccounts()
|
accounts = await keyring.getPreviousPage()
|
||||||
let keyring = await keyringController.getKeyringsByType(
|
break
|
||||||
'Trezor Hardware'
|
case 1:
|
||||||
)[0]
|
accounts = await keyring.getNextPage()
|
||||||
if (!keyring) {
|
break
|
||||||
keyring = await this.keyringController.addNewKeyring('Trezor Hardware')
|
default:
|
||||||
}
|
accounts = await keyring.getFirstPage()
|
||||||
let accounts = []
|
|
||||||
|
|
||||||
switch (page) {
|
|
||||||
case -1:
|
|
||||||
accounts = await keyring.getPreviousPage()
|
|
||||||
break
|
|
||||||
case 1:
|
|
||||||
accounts = await keyring.getNextPage()
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
accounts = await keyring.getFirstPage()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge with existing accounts
|
|
||||||
// and make sure addresses are not repeated
|
|
||||||
const accountsToTrack = [...new Set(oldAccounts.concat(accounts.map(a => a.address.toLowerCase())))]
|
|
||||||
this.accountTracker.syncWithAddresses(accountsToTrack)
|
|
||||||
return accounts
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error('MetamaskController:connectHardware - Unknown device')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge with existing accounts
|
||||||
|
// and make sure addresses are not repeated
|
||||||
|
const oldAccounts = await this.keyringController.getAccounts()
|
||||||
|
const accountsToTrack = [...new Set(oldAccounts.concat(accounts.map(a => a.address.toLowerCase())))]
|
||||||
|
this.accountTracker.syncWithAddresses(accountsToTrack)
|
||||||
|
return accounts
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -586,21 +636,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @returns {Promise<boolean>}
|
* @returns {Promise<boolean>}
|
||||||
*/
|
*/
|
||||||
async checkHardwareStatus (deviceName) {
|
async checkHardwareStatus (deviceName, hdPath) {
|
||||||
|
const keyring = await this.getKeyringForDevice(deviceName, hdPath)
|
||||||
switch (deviceName) {
|
return keyring.isUnlocked()
|
||||||
case 'trezor':
|
|
||||||
const keyringController = this.keyringController
|
|
||||||
const keyring = await keyringController.getKeyringsByType(
|
|
||||||
'Trezor Hardware'
|
|
||||||
)[0]
|
|
||||||
if (!keyring) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return keyring.isUnlocked()
|
|
||||||
default:
|
|
||||||
throw new Error('MetamaskController:checkHardwareStatus - Unknown device')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -610,20 +648,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
async forgetDevice (deviceName) {
|
async forgetDevice (deviceName) {
|
||||||
|
|
||||||
switch (deviceName) {
|
const keyring = await this.getKeyringForDevice(deviceName)
|
||||||
case 'trezor':
|
keyring.forgetDevice()
|
||||||
const keyringController = this.keyringController
|
return true
|
||||||
const keyring = await keyringController.getKeyringsByType(
|
|
||||||
'Trezor Hardware'
|
|
||||||
)[0]
|
|
||||||
if (!keyring) {
|
|
||||||
throw new Error('MetamaskController:forgetDevice - Trezor Hardware keyring not found')
|
|
||||||
}
|
|
||||||
keyring.forgetDevice()
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
throw new Error('MetamaskController:forgetDevice - Unknown device')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -631,23 +658,17 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @returns {} keyState
|
* @returns {} keyState
|
||||||
*/
|
*/
|
||||||
async unlockTrezorAccount (index) {
|
async unlockHardwareWalletAccount (index, deviceName, hdPath) {
|
||||||
const keyringController = this.keyringController
|
const keyring = await this.getKeyringForDevice(deviceName, hdPath)
|
||||||
const keyring = await keyringController.getKeyringsByType(
|
|
||||||
'Trezor Hardware'
|
|
||||||
)[0]
|
|
||||||
if (!keyring) {
|
|
||||||
throw new Error('MetamaskController - No Trezor Hardware Keyring found')
|
|
||||||
}
|
|
||||||
|
|
||||||
keyring.setAccountToUnlock(index)
|
keyring.setAccountToUnlock(index)
|
||||||
const oldAccounts = await keyringController.getAccounts()
|
const oldAccounts = await this.keyringController.getAccounts()
|
||||||
const keyState = await keyringController.addNewAccount(keyring)
|
const keyState = await this.keyringController.addNewAccount(keyring)
|
||||||
const newAccounts = await keyringController.getAccounts()
|
const newAccounts = await this.keyringController.getAccounts()
|
||||||
this.preferencesController.setAddresses(newAccounts)
|
this.preferencesController.setAddresses(newAccounts)
|
||||||
newAccounts.forEach(address => {
|
newAccounts.forEach(address => {
|
||||||
if (!oldAccounts.includes(address)) {
|
if (!oldAccounts.includes(address)) {
|
||||||
this.preferencesController.setAccountLabel(address, `TREZOR #${parseInt(index, 10) + 1}`)
|
this.preferencesController.setAccountLabel(address, `${deviceName.toUpperCase()} ${parseInt(index, 10) + 1}`)
|
||||||
this.preferencesController.setSelectedAddress(address)
|
this.preferencesController.setSelectedAddress(address)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -809,6 +830,18 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Identity Management (signature operations)
|
// Identity Management (signature operations)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a Dapp suggests a new tx to be signed.
|
||||||
|
* this wrapper needs to exist so we can provide a reference to
|
||||||
|
* "newUnapprovedTransaction" before "txController" is instantiated
|
||||||
|
*
|
||||||
|
* @param {Object} msgParams - The params passed to eth_sign.
|
||||||
|
* @param {Object} req - (optional) the original request, containing the origin
|
||||||
|
*/
|
||||||
|
async newUnapprovedTransaction (txParams, req) {
|
||||||
|
return await this.txController.newUnapprovedTransaction(txParams, req)
|
||||||
|
}
|
||||||
|
|
||||||
// eth_sign methods:
|
// eth_sign methods:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -820,20 +853,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
* @param {Object} msgParams - The params passed to eth_sign.
|
* @param {Object} msgParams - The params passed to eth_sign.
|
||||||
* @param {Function} cb = The callback function called with the signature.
|
* @param {Function} cb = The callback function called with the signature.
|
||||||
*/
|
*/
|
||||||
newUnsignedMessage (msgParams, cb) {
|
newUnsignedMessage (msgParams, req) {
|
||||||
const msgId = this.messageManager.addUnapprovedMessage(msgParams)
|
const promise = this.messageManager.addUnapprovedMessageAsync(msgParams, req)
|
||||||
this.sendUpdate()
|
this.sendUpdate()
|
||||||
this.opts.showUnconfirmedMessage()
|
this.opts.showUnconfirmedMessage()
|
||||||
this.messageManager.once(`${msgId}:finished`, (data) => {
|
return promise
|
||||||
switch (data.status) {
|
|
||||||
case 'signed':
|
|
||||||
return cb(null, data.rawSig)
|
|
||||||
case 'rejected':
|
|
||||||
return cb(cleanErrorStack(new Error('MetaMask Message Signature: User denied message signature.')))
|
|
||||||
default:
|
|
||||||
return cb(cleanErrorStack(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -887,24 +911,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
* @param {Function} cb - The callback function called with the signature.
|
* @param {Function} cb - The callback function called with the signature.
|
||||||
* Passed back to the requesting Dapp.
|
* Passed back to the requesting Dapp.
|
||||||
*/
|
*/
|
||||||
newUnsignedPersonalMessage (msgParams, cb) {
|
async newUnsignedPersonalMessage (msgParams, req) {
|
||||||
if (!msgParams.from) {
|
const promise = this.personalMessageManager.addUnapprovedMessageAsync(msgParams, req)
|
||||||
return cb(cleanErrorStack(new Error('MetaMask Message Signature: from field is required.')))
|
|
||||||
}
|
|
||||||
|
|
||||||
const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
|
|
||||||
this.sendUpdate()
|
this.sendUpdate()
|
||||||
this.opts.showUnconfirmedMessage()
|
this.opts.showUnconfirmedMessage()
|
||||||
this.personalMessageManager.once(`${msgId}:finished`, (data) => {
|
return promise
|
||||||
switch (data.status) {
|
|
||||||
case 'signed':
|
|
||||||
return cb(null, data.rawSig)
|
|
||||||
case 'rejected':
|
|
||||||
return cb(cleanErrorStack(new Error('MetaMask Message Signature: User denied message signature.')))
|
|
||||||
default:
|
|
||||||
return cb(cleanErrorStack(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -953,26 +964,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
* @param {Object} msgParams - The params passed to eth_signTypedData.
|
* @param {Object} msgParams - The params passed to eth_signTypedData.
|
||||||
* @param {Function} cb - The callback function, called with the signature.
|
* @param {Function} cb - The callback function, called with the signature.
|
||||||
*/
|
*/
|
||||||
newUnsignedTypedMessage (msgParams, cb) {
|
newUnsignedTypedMessage (msgParams, req) {
|
||||||
let msgId
|
const promise = this.typedMessageManager.addUnapprovedMessageAsync(msgParams, req)
|
||||||
try {
|
this.sendUpdate()
|
||||||
msgId = this.typedMessageManager.addUnapprovedMessage(msgParams)
|
this.opts.showUnconfirmedMessage()
|
||||||
this.sendUpdate()
|
return promise
|
||||||
this.opts.showUnconfirmedMessage()
|
|
||||||
} catch (e) {
|
|
||||||
return cb(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.typedMessageManager.once(`${msgId}:finished`, (data) => {
|
|
||||||
switch (data.status) {
|
|
||||||
case 'signed':
|
|
||||||
return cb(null, data.rawSig)
|
|
||||||
case 'rejected':
|
|
||||||
return cb(cleanErrorStack(new Error('MetaMask Message Signature: User denied message signature.')))
|
|
||||||
default:
|
|
||||||
return cb(cleanErrorStack(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1237,7 +1233,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
// create filter polyfill middleware
|
// create filter polyfill middleware
|
||||||
const filterMiddleware = createFilterMiddleware({
|
const filterMiddleware = createFilterMiddleware({
|
||||||
provider: this.provider,
|
provider: this.provider,
|
||||||
blockTracker: this.provider._blockTracker,
|
blockTracker: this.blockTracker,
|
||||||
})
|
})
|
||||||
|
|
||||||
engine.push(createOriginMiddleware({ origin }))
|
engine.push(createOriginMiddleware({ origin }))
|
||||||
|
@ -24,8 +24,13 @@ class ExtensionPlatform {
|
|||||||
return extension.runtime.getManifest().version
|
return extension.runtime.getManifest().version
|
||||||
}
|
}
|
||||||
|
|
||||||
openExtensionInBrowser (route = null) {
|
openExtensionInBrowser (route = null, queryString = null) {
|
||||||
let extensionURL = extension.runtime.getURL('home.html')
|
let extensionURL = extension.runtime.getURL('home.html')
|
||||||
|
|
||||||
|
if (queryString) {
|
||||||
|
extensionURL += `?${queryString}`
|
||||||
|
}
|
||||||
|
|
||||||
if (route) {
|
if (route) {
|
||||||
extensionURL += `#${route}`
|
extensionURL += `#${route}`
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
### Developing on Dependencies
|
### Developing on Dependencies
|
||||||
|
|
||||||
To enjoy the live-reloading that `gulp dev` offers while working on the `web3-provider-engine` or other dependencies:
|
To enjoy the live-reloading that `gulp dev` offers while working on the dependencies:
|
||||||
|
|
||||||
1. Clone the dependency locally.
|
1. Clone the dependency locally.
|
||||||
2. `npm install` in its folder.
|
2. `npm install` in its folder.
|
||||||
3. Run `npm link` in its folder.
|
3. Run `npm link` in its folder.
|
||||||
4. Run `npm link $DEP_NAME` in this project folder.
|
4. Run `npm link $DEP_NAME` in this project folder.
|
||||||
5. Next time you `npm start` it will watch the dependency for changes as well!
|
5. Next time you `npm start` it will watch the dependency for changes as well!
|
||||||
|
|
||||||
|
@ -89,8 +89,6 @@ MetaMask has two kinds of [duplex stream APIs](https://github.com/substack/strea
|
|||||||
|
|
||||||
If you are making a MetaMask-powered browser for a new platform, one of the trickiest tasks will be injecting the Web3 API into websites that are visited. On WebExtensions, we actually have to pipe data through a total of three JS contexts just to let sites talk to our background process (site -> contentscript -> background).
|
If you are making a MetaMask-powered browser for a new platform, one of the trickiest tasks will be injecting the Web3 API into websites that are visited. On WebExtensions, we actually have to pipe data through a total of three JS contexts just to let sites talk to our background process (site -> contentscript -> background).
|
||||||
|
|
||||||
To make this as easy as possible, we use one of our favorite internal tools, [web3-provider-engine](https://www.npmjs.com/package/web3-provider-engine) to construct a custom web3 provider object whose source of truth is a stream that we connect to remotely.
|
|
||||||
|
|
||||||
To see how we do that, you can refer to the [inpage script](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/inpage.js) that we inject into every website. There you can see it creates a multiplex stream to the background, and uses it to initialize what we call the [inpage-provider](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/lib/inpage-provider.js), which you can see stubs a few methods out, but mostly just passes calls to `sendAsync` through the stream it's passed! That's really all the magic that's needed to create a web3-like API in a remote context, once you have a stream to MetaMask available.
|
To see how we do that, you can refer to the [inpage script](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/inpage.js) that we inject into every website. There you can see it creates a multiplex stream to the background, and uses it to initialize what we call the [inpage-provider](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/lib/inpage-provider.js), which you can see stubs a few methods out, but mostly just passes calls to `sendAsync` through the stream it's passed! That's really all the magic that's needed to create a web3-like API in a remote context, once you have a stream to MetaMask available.
|
||||||
|
|
||||||
In `inpage.js` you can see we create a `PortStream`, that's just a class we use to wrap WebExtension ports as streams, so we can reuse our favorite stream abstraction over the more irregular API surface of the WebExtension. In a new platform, you will probably need to construct this stream differently. The key is that you need to construct a stream that talks from the site context to the background. Once you have that set up, it works like magic!
|
In `inpage.js` you can see we create a `PortStream`, that's just a class we use to wrap WebExtension ports as streams, so we can reuse our favorite stream abstraction over the more irregular API surface of the WebExtension. In a new platform, you will probably need to construct this stream differently. The key is that you need to construct a stream that talks from the site context to the background. Once you have that set up, it works like magic!
|
||||||
|
@ -340,6 +340,19 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.backup-phrase__tips-text--link {
|
||||||
|
color: #2f9ae0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backup-phrase__tips-text--link:hover {
|
||||||
|
color: #2f9ae0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backup-phrase__tips-text--strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 768px) {
|
@media only screen and (max-width: 768px) {
|
||||||
.backup-phrase__content-wrapper {
|
.backup-phrase__content-wrapper {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -5,6 +5,7 @@ import classnames from 'classnames'
|
|||||||
import { withRouter } from 'react-router-dom'
|
import { withRouter } from 'react-router-dom'
|
||||||
import { compose } from 'recompose'
|
import { compose } from 'recompose'
|
||||||
import Identicon from '../../../../ui/app/components/identicon'
|
import Identicon from '../../../../ui/app/components/identicon'
|
||||||
|
import {exportAsFile} from '../../../../ui/app/util'
|
||||||
import Breadcrumbs from './breadcrumbs'
|
import Breadcrumbs from './breadcrumbs'
|
||||||
import LoadingScreen from './loading-screen'
|
import LoadingScreen from './loading-screen'
|
||||||
import { DEFAULT_ROUTE, INITIALIZE_CONFIRM_SEED_ROUTE } from '../../../../ui/app/routes'
|
import { DEFAULT_ROUTE, INITIALIZE_CONFIRM_SEED_ROUTE } from '../../../../ui/app/routes'
|
||||||
@ -65,6 +66,12 @@ class BackupPhraseScreen extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportSeedWords = () => {
|
||||||
|
const { seedWords } = this.props
|
||||||
|
|
||||||
|
exportAsFile('MetaMask Secret Backup Phrase', seedWords, 'text/plain')
|
||||||
|
}
|
||||||
|
|
||||||
renderSecretWordsContainer () {
|
renderSecretWordsContainer () {
|
||||||
const { isShowingSecret } = this.state
|
const { isShowingSecret } = this.state
|
||||||
|
|
||||||
@ -111,7 +118,7 @@ class BackupPhraseScreen extends Component {
|
|||||||
<div className="backup-phrase__tips">
|
<div className="backup-phrase__tips">
|
||||||
<div className="backup-phrase__tips-text">Tips:</div>
|
<div className="backup-phrase__tips-text">Tips:</div>
|
||||||
<div className="backup-phrase__tips-text">
|
<div className="backup-phrase__tips-text">
|
||||||
Store this phrase in a password manager like 1password.
|
Store this phrase in a password manager like 1Password.
|
||||||
</div>
|
</div>
|
||||||
<div className="backup-phrase__tips-text">
|
<div className="backup-phrase__tips-text">
|
||||||
Write this phrase on a piece of paper and store in a secure location. If you want even more security, write it down on multiple pieces of paper and store each in 2 - 3 different locations.
|
Write this phrase on a piece of paper and store in a secure location. If you want even more security, write it down on multiple pieces of paper and store each in 2 - 3 different locations.
|
||||||
@ -119,6 +126,13 @@ class BackupPhraseScreen extends Component {
|
|||||||
<div className="backup-phrase__tips-text">
|
<div className="backup-phrase__tips-text">
|
||||||
Memorize this phrase.
|
Memorize this phrase.
|
||||||
</div>
|
</div>
|
||||||
|
<div className="backup-phrase__tips-text">
|
||||||
|
<strong>
|
||||||
|
<a className="backup-phrase__tips-text--link backup-phrase__tips-text--strong" onClick={this.exportSeedWords}>
|
||||||
|
Download this Secret Backup Phrase
|
||||||
|
</a>
|
||||||
|
</strong> and keep it stored safely on an external encrypted hard drive or storage medium.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="backup-phrase__next-button">
|
<div className="backup-phrase__next-button">
|
||||||
<button
|
<button
|
||||||
|
@ -116,12 +116,25 @@ Notice.prototype.render = function () {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Notice.prototype.setInitialDisclaimerState = function () {
|
||||||
|
if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) {
|
||||||
|
this.setState({disclaimerDisabled: false})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Notice.prototype.componentDidMount = function () {
|
Notice.prototype.componentDidMount = function () {
|
||||||
// eslint-disable-next-line react/no-find-dom-node
|
// eslint-disable-next-line react/no-find-dom-node
|
||||||
var node = findDOMNode(this)
|
var node = findDOMNode(this)
|
||||||
linker.setupListener(node)
|
linker.setupListener(node)
|
||||||
if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) {
|
this.setInitialDisclaimerState()
|
||||||
this.setState({disclaimerDisabled: false})
|
}
|
||||||
|
|
||||||
|
Notice.prototype.componentDidUpdate = function (prevProps) {
|
||||||
|
const { notice: { id } = {} } = this.props
|
||||||
|
const { notice: { id: prevNoticeId } = {} } = prevProps
|
||||||
|
|
||||||
|
if (id !== prevNoticeId) {
|
||||||
|
this.setInitialDisclaimerState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ TransactionListItem.prototype.showRetryButton = function () {
|
|||||||
const currentNonce = txParams.nonce
|
const currentNonce = txParams.nonce
|
||||||
const currentNonceTxs = transactions.filter(tx => tx.txParams.nonce === currentNonce)
|
const currentNonceTxs = transactions.filter(tx => tx.txParams.nonce === currentNonce)
|
||||||
const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted')
|
const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted')
|
||||||
const currentSubmittedTxs = transactions.filter(tx => tx.status === 'submitted')
|
const currentSubmittedTxs = transactions.filter(tx => tx.status === 'submitted')
|
||||||
const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[0]
|
const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[0]
|
||||||
const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce &&
|
const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce &&
|
||||||
lastSubmittedTxWithCurrentNonce.id === transaction.id
|
lastSubmittedTxWithCurrentNonce.id === transaction.id
|
||||||
|
@ -138,7 +138,6 @@ InfoScreen.prototype.render = function () {
|
|||||||
h('div.fa.fa-envelope', [
|
h('div.fa.fa-envelope', [
|
||||||
h('a.info', {
|
h('a.info', {
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
style: { width: '85vw' },
|
|
||||||
href: 'mailto:help@metamask.io?subject=Feedback',
|
href: 'mailto:help@metamask.io?subject=Feedback',
|
||||||
}, 'Email us!'),
|
}, 'Email us!'),
|
||||||
]),
|
]),
|
||||||
|
9862
package-lock.json
generated
9862
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@ -36,7 +36,7 @@
|
|||||||
"test:mascara:build:locales": "mkdirp dist/chrome && cp -R app/_locales dist/chrome/_locales",
|
"test:mascara:build:locales": "mkdirp dist/chrome && cp -R app/_locales dist/chrome/_locales",
|
||||||
"test:mascara:build:background": "browserify mascara/src/background.js -o dist/mascara/background.js",
|
"test:mascara:build:background": "browserify mascara/src/background.js -o dist/mascara/background.js",
|
||||||
"test:mascara:build:tests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js",
|
"test:mascara:build:tests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js",
|
||||||
"ganache:start": "ganache-cli -m 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'",
|
"ganache:start": "ganache-cli --noVMErrorsOnRPCResponse -m 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'",
|
||||||
"sentry:publish": "node ./development/sentry-publish.js",
|
"sentry:publish": "node ./development/sentry-publish.js",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
@ -75,6 +75,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material-ui/core": "1.0.0",
|
"@material-ui/core": "1.0.0",
|
||||||
|
"@zxing/library": "^0.8.0",
|
||||||
"abi-decoder": "^1.0.9",
|
"abi-decoder": "^1.0.9",
|
||||||
"asmcrypto.js": "0.22.0",
|
"asmcrypto.js": "0.22.0",
|
||||||
"async": "^2.5.0",
|
"async": "^2.5.0",
|
||||||
@ -97,17 +98,22 @@
|
|||||||
"debounce-stream": "^2.0.0",
|
"debounce-stream": "^2.0.0",
|
||||||
"deep-extend": "^0.5.1",
|
"deep-extend": "^0.5.1",
|
||||||
"detect-node": "^2.0.3",
|
"detect-node": "^2.0.3",
|
||||||
|
"detectrtc": "^1.3.6",
|
||||||
"disc": "^1.3.2",
|
"disc": "^1.3.2",
|
||||||
"dnode": "^1.2.2",
|
"dnode": "^1.2.2",
|
||||||
"end-of-stream": "^1.1.0",
|
"end-of-stream": "^1.1.0",
|
||||||
"ensnare": "^1.0.0",
|
"ensnare": "^1.0.0",
|
||||||
"eslint-plugin-react": "^7.4.0",
|
"eslint-plugin-react": "^7.4.0",
|
||||||
"eth-bin-to-ops": "^1.0.1",
|
"eth-bin-to-ops": "^1.0.1",
|
||||||
|
"eth-block-tracker": "^4.0.1",
|
||||||
"eth-contract-metadata": "github:MetaMask/eth-contract-metadata#master",
|
"eth-contract-metadata": "github:MetaMask/eth-contract-metadata#master",
|
||||||
|
"eth-json-rpc-middleware": "^2.4.0",
|
||||||
|
"eth-keyring-controller": "^3.1.4",
|
||||||
"eth-ens-namehash": "^2.0.8",
|
"eth-ens-namehash": "^2.0.8",
|
||||||
"eth-hd-keyring": "^1.2.2",
|
"eth-hd-keyring": "^1.2.2",
|
||||||
"eth-json-rpc-filters": "^1.2.6",
|
"eth-json-rpc-filters": "^2.1.1",
|
||||||
"eth-json-rpc-infura": "^3.0.0",
|
"eth-json-rpc-infura": "^3.0.0",
|
||||||
|
"eth-ledger-bridge-keyring": "^0.1.0",
|
||||||
"eth-method-registry": "^1.0.0",
|
"eth-method-registry": "^1.0.0",
|
||||||
"eth-phishing-detect": "^1.1.4",
|
"eth-phishing-detect": "^1.1.4",
|
||||||
"eth-query": "^2.1.2",
|
"eth-query": "^2.1.2",
|
||||||
@ -143,7 +149,7 @@
|
|||||||
"iframe-stream": "^3.0.0",
|
"iframe-stream": "^3.0.0",
|
||||||
"inject-css": "^0.1.1",
|
"inject-css": "^0.1.1",
|
||||||
"jazzicon": "^1.2.0",
|
"jazzicon": "^1.2.0",
|
||||||
"json-rpc-engine": "^3.6.1",
|
"json-rpc-engine": "^3.7.3",
|
||||||
"json-rpc-middleware-stream": "^1.0.1",
|
"json-rpc-middleware-stream": "^1.0.1",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"lodash.memoize": "^4.1.2",
|
"lodash.memoize": "^4.1.2",
|
||||||
@ -200,12 +206,13 @@
|
|||||||
"shallow-copy": "0.0.1",
|
"shallow-copy": "0.0.1",
|
||||||
"sw-controller": "^1.0.3",
|
"sw-controller": "^1.0.3",
|
||||||
"sw-stream": "^2.0.2",
|
"sw-stream": "^2.0.2",
|
||||||
|
"swappable-obj-proxy": "^1.0.2",
|
||||||
"textarea-caret": "^3.0.1",
|
"textarea-caret": "^3.0.1",
|
||||||
"valid-url": "^1.0.9",
|
"valid-url": "^1.0.9",
|
||||||
"vreme": "^3.0.2",
|
"vreme": "^3.0.2",
|
||||||
"web3": "^0.20.1",
|
"web3": "^0.20.1",
|
||||||
"web3-provider-engine": "^14.0.5",
|
|
||||||
"web3-stream-provider": "^3.0.1",
|
"web3-stream-provider": "^3.0.1",
|
||||||
|
"webrtc-adapter": "^6.3.0",
|
||||||
"xtend": "^4.0.1"
|
"xtend": "^4.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -246,6 +253,7 @@
|
|||||||
"eth-json-rpc-middleware": "^1.6.0",
|
"eth-json-rpc-middleware": "^1.6.0",
|
||||||
"eth-keyring-controller": "^3.3.1",
|
"eth-keyring-controller": "^3.3.1",
|
||||||
"file-loader": "^1.1.11",
|
"file-loader": "^1.1.11",
|
||||||
|
"fs-extra": "^6.0.1",
|
||||||
"fs-promise": "^2.0.3",
|
"fs-promise": "^2.0.3",
|
||||||
"ganache-cli": "^6.1.0",
|
"ganache-cli": "^6.1.0",
|
||||||
"ganache-core": "^2.1.5",
|
"ganache-core": "^2.1.5",
|
||||||
@ -290,6 +298,7 @@
|
|||||||
"open": "0.0.5",
|
"open": "0.0.5",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"png-file-stream": "^1.0.0",
|
"png-file-stream": "^1.0.0",
|
||||||
|
"prepend-file": "^1.3.1",
|
||||||
"prompt": "^1.0.0",
|
"prompt": "^1.0.0",
|
||||||
"proxyquire": "2.0.1",
|
"proxyquire": "2.0.1",
|
||||||
"qs": "^6.2.0",
|
"qs": "^6.2.0",
|
||||||
|
@ -38,18 +38,20 @@ const transferTokens = document.getElementById('transferTokens')
|
|||||||
const approveTokens = document.getElementById('approveTokens')
|
const approveTokens = document.getElementById('approveTokens')
|
||||||
|
|
||||||
deployButton.addEventListener('click', async function (event) {
|
deployButton.addEventListener('click', async function (event) {
|
||||||
|
document.getElementById('contractStatus').innerHTML = 'Deploying'
|
||||||
|
|
||||||
var piggybank = await piggybankContract.new(
|
var piggybank = await piggybankContract.new(
|
||||||
{
|
{
|
||||||
from: web3.eth.accounts[0],
|
from: web3.eth.accounts[0],
|
||||||
data: '0x608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000808190555061023b806100686000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d1461005c5780638da5cb5b1461009d578063d0e30db0146100f4575b600080fd5b34801561006857600080fd5b5061008760048036038101908080359060200190929190505050610112565b6040518082815260200191505060405180910390f35b3480156100a957600080fd5b506100b26101d0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100fc6101f6565b6040518082815260200191505060405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561017057600080fd5b8160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f193505050501580156101c5573d6000803e3d6000fd5b506000549050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003460008082825401925050819055506000549050905600a165627a7a72305820f237db3ec816a52589d82512117bc85bc08d3537683ffeff9059108caf3e5d400029',
|
data: '0x608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000808190555061023b806100686000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d1461005c5780638da5cb5b1461009d578063d0e30db0146100f4575b600080fd5b34801561006857600080fd5b5061008760048036038101908080359060200190929190505050610112565b6040518082815260200191505060405180910390f35b3480156100a957600080fd5b506100b26101d0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100fc6101f6565b6040518082815260200191505060405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561017057600080fd5b8160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f193505050501580156101c5573d6000803e3d6000fd5b506000549050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003460008082825401925050819055506000549050905600a165627a7a72305820f237db3ec816a52589d82512117bc85bc08d3537683ffeff9059108caf3e5d400029',
|
||||||
gas: '4700000',
|
gas: '4700000',
|
||||||
}, function (e, contract) {
|
}, function (e, contract) {
|
||||||
console.log(e, contract)
|
if (e) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
if (typeof contract.address !== 'undefined') {
|
if (typeof contract.address !== 'undefined') {
|
||||||
console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash)
|
console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash)
|
||||||
|
|
||||||
console.log(`contract`, contract)
|
|
||||||
|
|
||||||
document.getElementById('contractStatus').innerHTML = 'Deployed'
|
document.getElementById('contractStatus').innerHTML = 'Deployed'
|
||||||
|
|
||||||
depositButton.addEventListener('click', function (event) {
|
depositButton.addEventListener('click', function (event) {
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<button id="withdrawButton">Withdraw</button>
|
<button id="withdrawButton">Withdraw</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="contractStatus" style="display: flex; font-size: 1rem;">
|
<div id="contractStatus" style="display: flex; font-size: 1rem;">
|
||||||
Not yet deployed
|
Not clicked
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; flex-flow: column;">
|
<div style="display: flex; flex-flow: column;">
|
||||||
|
@ -89,7 +89,14 @@ describe('Using MetaMask with an existing account', function () {
|
|||||||
await driver.wait(until.stalenessOf(overlay))
|
await driver.wait(until.stalenessOf(overlay))
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
const button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
|
let button
|
||||||
|
try {
|
||||||
|
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
|
||||||
|
} catch (e) {
|
||||||
|
await loadExtension(driver, extensionId)
|
||||||
|
await delay(largeDelayMs)
|
||||||
|
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
|
||||||
|
}
|
||||||
await button.click()
|
await button.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
@ -359,7 +366,10 @@ describe('Using MetaMask with an existing account', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should open the TREZOR Connect popup', async () => {
|
it('should open the TREZOR Connect popup', async () => {
|
||||||
const connectButtons = await findElements(driver, By.xpath(`//button[contains(text(), 'Connect to Trezor')]`))
|
const trezorButton = await findElements(driver, By.css('.hw-connect__btn'))
|
||||||
|
await trezorButton[1].click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
const connectButtons = await findElements(driver, By.xpath(`//button[contains(text(), 'Connect')]`))
|
||||||
await connectButtons[0].click()
|
await connectButtons[0].click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
const allWindows = await driver.getAllWindowHandles()
|
const allWindows = await driver.getAllWindowHandles()
|
||||||
|
@ -2,8 +2,8 @@ const fs = require('fs')
|
|||||||
const mkdirp = require('mkdirp')
|
const mkdirp = require('mkdirp')
|
||||||
const pify = require('pify')
|
const pify = require('pify')
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const {until} = require('selenium-webdriver')
|
|
||||||
const { delay } = require('../func')
|
const { delay } = require('../func')
|
||||||
|
const { until } = require('selenium-webdriver')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
assertElementNotPresent,
|
assertElementNotPresent,
|
||||||
@ -122,12 +122,14 @@ async function closeAllWindowHandlesExcept (driver, exceptions, windowHandles) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function assertElementNotPresent (webdriver, driver, by) {
|
async function assertElementNotPresent (webdriver, driver, by) {
|
||||||
|
let dataTab
|
||||||
try {
|
try {
|
||||||
const dataTab = await findElement(driver, by, 4000)
|
dataTab = await findElement(driver, by, 4000)
|
||||||
if (dataTab) {
|
|
||||||
assert(false, 'Data tab should not be present')
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
assert(err instanceof webdriver.error.NoSuchElementError)
|
console.log(err)
|
||||||
|
assert(err instanceof webdriver.error.NoSuchElementError || err instanceof webdriver.error.TimeoutError)
|
||||||
|
}
|
||||||
|
if (dataTab) {
|
||||||
|
assert(false, 'Data tab should not be present')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,14 @@ describe('MetaMask', function () {
|
|||||||
await driver.wait(until.stalenessOf(overlay))
|
await driver.wait(until.stalenessOf(overlay))
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
const button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
|
let button
|
||||||
|
try {
|
||||||
|
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
|
||||||
|
} catch (e) {
|
||||||
|
await loadExtension(driver, extensionId)
|
||||||
|
await delay(largeDelayMs)
|
||||||
|
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
|
||||||
|
}
|
||||||
await button.click()
|
await button.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
@ -345,8 +352,8 @@ describe('MetaMask', function () {
|
|||||||
const passwordInputs = await driver.findElements(By.css('input'))
|
const passwordInputs = await driver.findElements(By.css('input'))
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
passwordInputs[0].sendKeys('correct horse battery staple')
|
await passwordInputs[0].sendKeys('correct horse battery staple')
|
||||||
passwordInputs[1].sendKeys('correct horse battery staple')
|
await passwordInputs[1].sendKeys('correct horse battery staple')
|
||||||
await driver.findElement(By.css('.first-time-flow__button')).click()
|
await driver.findElement(By.css('.first-time-flow__button')).click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
@ -438,7 +445,7 @@ describe('MetaMask', function () {
|
|||||||
await driver.switchTo().window(windowHandles[2])
|
await driver.switchTo().window(windowHandles[2])
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
assertElementNotPresent(webdriver, driver, By.xpath(`//li[contains(text(), 'Data')]`))
|
await assertElementNotPresent(webdriver, driver, By.xpath(`//li[contains(text(), 'Data')]`))
|
||||||
|
|
||||||
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`), 10000)
|
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`), 10000)
|
||||||
await confirmButton.click()
|
await confirmButton.click()
|
||||||
@ -453,6 +460,11 @@ describe('MetaMask', function () {
|
|||||||
const transactions = await findElements(driver, By.css('.tx-list-item'))
|
const transactions = await findElements(driver, By.css('.tx-list-item'))
|
||||||
assert.equal(transactions.length, 2)
|
assert.equal(transactions.length, 2)
|
||||||
|
|
||||||
|
await findElement(driver, By.xpath(`//span[contains(text(), 'Submitted')]`))
|
||||||
|
|
||||||
|
const txStatuses = await findElements(driver, By.css('.tx-list-status'))
|
||||||
|
await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed/))
|
||||||
|
|
||||||
const txValues = await findElement(driver, By.css('.tx-list-value'))
|
const txValues = await findElement(driver, By.css('.tx-list-value'))
|
||||||
await driver.wait(until.elementTextMatches(txValues, /3\sETH/), 10000)
|
await driver.wait(until.elementTextMatches(txValues, /3\sETH/), 10000)
|
||||||
})
|
})
|
||||||
@ -503,6 +515,8 @@ describe('MetaMask', function () {
|
|||||||
await confirmButton.click()
|
await confirmButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
await findElement(driver, By.xpath(`//span[contains(text(), 'Submitted')]`))
|
||||||
|
|
||||||
const txStatuses = await findElements(driver, By.css('.tx-list-status'))
|
const txStatuses = await findElements(driver, By.css('.tx-list-status'))
|
||||||
await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed/))
|
await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed/))
|
||||||
|
|
||||||
@ -511,19 +525,28 @@ describe('MetaMask', function () {
|
|||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('confirms a deploy contract transaction in the popup', async () => {
|
||||||
|
const windowHandles = await driver.getAllWindowHandles()
|
||||||
|
const popup = windowHandles[2]
|
||||||
|
await driver.switchTo().window(popup)
|
||||||
|
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||||
|
await confirmButton.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
it('calls and confirms a contract method where ETH is sent', async () => {
|
it('calls and confirms a contract method where ETH is sent', async () => {
|
||||||
await driver.switchTo().window(dapp)
|
await driver.switchTo().window(dapp)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
let contractStatus = await driver.findElement(By.css('#contractStatus'))
|
let contractStatus = await findElement(driver, By.css('#contractStatus'))
|
||||||
await driver.wait(until.elementTextMatches(contractStatus, /Deployed/))
|
await driver.wait(until.elementTextMatches(contractStatus, /Deployed/), 15000)
|
||||||
|
|
||||||
const depositButton = await findElement(driver, By.css('#depositButton'))
|
const depositButton = await findElement(driver, By.css('#depositButton'))
|
||||||
await depositButton.click()
|
await depositButton.click()
|
||||||
await delay(largeDelayMs)
|
await delay(largeDelayMs)
|
||||||
|
|
||||||
contractStatus = await driver.findElement(By.css('#contractStatus'))
|
contractStatus = await findElement(driver, By.css('#contractStatus'))
|
||||||
await driver.wait(until.elementTextMatches(contractStatus, /Deposit\sinitiated/))
|
await driver.wait(until.elementTextMatches(contractStatus, /Deposit\sinitiated/), 10000)
|
||||||
|
|
||||||
await driver.switchTo().window(extension)
|
await driver.switchTo().window(extension)
|
||||||
await delay(largeDelayMs)
|
await delay(largeDelayMs)
|
||||||
@ -539,8 +562,8 @@ describe('MetaMask', function () {
|
|||||||
await configureGas.click()
|
await configureGas.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const gasModal = await driver.findElement(By.css('span .modal'))
|
const gasModal = await findElement(driver, By.css('span .modal'))
|
||||||
await driver.wait(until.elementLocated(By.css('.customize-gas__title')))
|
await driver.wait(until.elementLocated(By.css('.customize-gas__title')), 10000)
|
||||||
|
|
||||||
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.customize-gas-input'))
|
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.customize-gas-input'))
|
||||||
await gasPriceInput.clear()
|
await gasPriceInput.clear()
|
||||||
@ -612,20 +635,21 @@ describe('MetaMask', function () {
|
|||||||
|
|
||||||
describe('Add a custom token from a dapp', () => {
|
describe('Add a custom token from a dapp', () => {
|
||||||
it('creates a new token', async () => {
|
it('creates a new token', async () => {
|
||||||
const windowHandles = await driver.getAllWindowHandles()
|
let windowHandles = await driver.getAllWindowHandles()
|
||||||
const extension = windowHandles[0]
|
const extension = windowHandles[0]
|
||||||
const dapp = windowHandles[1]
|
const dapp = windowHandles[1]
|
||||||
await delay(regularDelayMs * 2)
|
await delay(regularDelayMs * 2)
|
||||||
|
|
||||||
await driver.switchTo().window(dapp)
|
await driver.switchTo().window(dapp)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs * 2)
|
||||||
|
|
||||||
const createToken = await findElement(driver, 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(largeDelayMs)
|
||||||
|
|
||||||
await driver.switchTo().window(extension)
|
windowHandles = await driver.getAllWindowHandles()
|
||||||
await loadExtension(driver, extensionId)
|
const popup = windowHandles[2]
|
||||||
|
await driver.switchTo().window(popup)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
|
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||||
@ -1000,4 +1024,4 @@ describe('MetaMask', function () {
|
|||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
@ -1,14 +1,19 @@
|
|||||||
require('chromedriver')
|
require('chromedriver')
|
||||||
require('geckodriver')
|
require('geckodriver')
|
||||||
const fs = require('fs')
|
const fs = require('fs-extra')
|
||||||
const os = require('os')
|
const os = require('os')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
const pify = require('pify')
|
||||||
|
const prependFile = pify(require('prepend-file'))
|
||||||
const webdriver = require('selenium-webdriver')
|
const webdriver = require('selenium-webdriver')
|
||||||
const Command = require('selenium-webdriver/lib/command').Command
|
const Command = require('selenium-webdriver/lib/command').Command
|
||||||
const By = webdriver.By
|
const By = webdriver.By
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
delay,
|
delay,
|
||||||
|
createModifiedTestBuild,
|
||||||
|
setupBrowserAndExtension,
|
||||||
|
verboseReportOnFailure,
|
||||||
buildChromeWebDriver,
|
buildChromeWebDriver,
|
||||||
buildFirefoxWebdriver,
|
buildFirefoxWebdriver,
|
||||||
installWebExt,
|
installWebExt,
|
||||||
@ -20,6 +25,37 @@ function delay (time) {
|
|||||||
return new Promise(resolve => setTimeout(resolve, time))
|
return new Promise(resolve => setTimeout(resolve, time))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createModifiedTestBuild ({ browser, srcPath }) {
|
||||||
|
// copy build to test-builds directory
|
||||||
|
const extPath = path.resolve(`test-builds/${browser}`)
|
||||||
|
await fs.ensureDir(extPath)
|
||||||
|
await fs.copy(srcPath, extPath)
|
||||||
|
// inject METAMASK_TEST_CONFIG setting default test network
|
||||||
|
const config = { NetworkController: { provider: { type: 'localhost' } } }
|
||||||
|
await prependFile(`${extPath}/background.js`, `window.METAMASK_TEST_CONFIG=${JSON.stringify(config)};\n`)
|
||||||
|
return { extPath }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupBrowserAndExtension ({ browser, extPath }) {
|
||||||
|
let driver, extensionId, extensionUri
|
||||||
|
|
||||||
|
if (browser === 'chrome') {
|
||||||
|
driver = buildChromeWebDriver(extPath)
|
||||||
|
extensionId = await getExtensionIdChrome(driver)
|
||||||
|
extensionUri = `chrome-extension://${extensionId}/home.html`
|
||||||
|
} else if (browser === 'firefox') {
|
||||||
|
driver = buildFirefoxWebdriver()
|
||||||
|
await installWebExt(driver, extPath)
|
||||||
|
await delay(700)
|
||||||
|
extensionId = await getExtensionIdFirefox(driver)
|
||||||
|
extensionUri = `moz-extension://${extensionId}/home.html`
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown Browser "${browser}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { driver, extensionId, extensionUri }
|
||||||
|
}
|
||||||
|
|
||||||
function buildChromeWebDriver (extPath) {
|
function buildChromeWebDriver (extPath) {
|
||||||
const tmpProfile = fs.mkdtempSync(path.join(os.tmpdir(), 'mm-chrome-profile'))
|
const tmpProfile = fs.mkdtempSync(path.join(os.tmpdir(), 'mm-chrome-profile'))
|
||||||
return new webdriver.Builder()
|
return new webdriver.Builder()
|
||||||
@ -61,3 +97,13 @@ async function installWebExt (driver, extension) {
|
|||||||
|
|
||||||
return await driver.schedule(cmd, 'installWebExt(' + extension + ')')
|
return await driver.schedule(cmd, 'installWebExt(' + extension + ')')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function verboseReportOnFailure ({ browser, driver, title }) {
|
||||||
|
const artifactDir = `./test-artifacts/${browser}/${title}`
|
||||||
|
const filepathBase = `${artifactDir}/test-failure`
|
||||||
|
await fs.ensureDir(artifactDir)
|
||||||
|
const screenshot = await driver.takeScreenshot()
|
||||||
|
await fs.writeFile(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' })
|
||||||
|
const htmlSource = await driver.getPageSource()
|
||||||
|
await fs.writeFile(`${filepathBase}-dom.html`, htmlSource)
|
||||||
|
}
|
||||||
|
@ -1,49 +1,41 @@
|
|||||||
const fs = require('fs')
|
|
||||||
const mkdirp = require('mkdirp')
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const pify = require('pify')
|
const { By, Key, until } = require('selenium-webdriver')
|
||||||
const webdriver = require('selenium-webdriver')
|
const { delay, createModifiedTestBuild, setupBrowserAndExtension, verboseReportOnFailure } = require('./func')
|
||||||
const { By, Key, until } = webdriver
|
|
||||||
const { delay, buildChromeWebDriver, buildFirefoxWebdriver, installWebExt, getExtensionIdChrome, getExtensionIdFirefox } = require('./func')
|
|
||||||
|
|
||||||
describe('Metamask popup page', function () {
|
describe('Metamask popup page', function () {
|
||||||
let driver, accountAddress, tokenAddress, extensionId
|
const browser = process.env.SELENIUM_BROWSER
|
||||||
|
let driver, accountAddress, tokenAddress, extensionUri
|
||||||
|
|
||||||
this.timeout(0)
|
this.timeout(0)
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
const srcPath = path.resolve(`dist/${browser}`)
|
||||||
const extPath = path.resolve('dist/chrome')
|
const { extPath } = await createModifiedTestBuild({ browser, srcPath })
|
||||||
driver = buildChromeWebDriver(extPath)
|
const installResult = await setupBrowserAndExtension({ browser, extPath })
|
||||||
extensionId = await getExtensionIdChrome(driver)
|
driver = installResult.driver
|
||||||
await driver.get(`chrome-extension://${extensionId}/popup.html`)
|
extensionUri = installResult.extensionUri
|
||||||
|
|
||||||
} else if (process.env.SELENIUM_BROWSER === 'firefox') {
|
await driver.get(extensionUri)
|
||||||
const extPath = path.resolve('dist/firefox')
|
await delay(300)
|
||||||
driver = buildFirefoxWebdriver()
|
|
||||||
await installWebExt(driver, extPath)
|
|
||||||
await delay(700)
|
|
||||||
extensionId = await getExtensionIdFirefox(driver)
|
|
||||||
await driver.get(`moz-extension://${extensionId}/popup.html`)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async function () {
|
afterEach(async function () {
|
||||||
// logs command not supported in firefox
|
// logs command not supported in firefox
|
||||||
// https://github.com/SeleniumHQ/selenium/issues/2910
|
// https://github.com/SeleniumHQ/selenium/issues/2910
|
||||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
if (browser === 'chrome') {
|
||||||
// check for console errors
|
// check for console errors
|
||||||
const errors = await checkBrowserForConsoleErrors()
|
const errors = await checkBrowserForConsoleErrors()
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
const errorReports = errors.map(err => err.message)
|
const errorReports = errors.map(err => err.message)
|
||||||
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
|
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
|
||||||
this.test.error(new Error(errorMessage))
|
console.error(new Error(errorMessage))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// gather extra data if test failed
|
// gather extra data if test failed
|
||||||
if (this.currentTest.state === 'failed') {
|
if (this.currentTest.state === 'failed') {
|
||||||
await verboseReportOnFailure(this.currentTest)
|
await verboseReportOnFailure({ browser, driver, title: this.currentTest.title })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -54,7 +46,6 @@ describe('Metamask popup page', function () {
|
|||||||
describe('Setup', function () {
|
describe('Setup', function () {
|
||||||
|
|
||||||
it('switches to Chrome extensions list', async function () {
|
it('switches to Chrome extensions list', async function () {
|
||||||
await delay(300)
|
|
||||||
const windowHandles = await driver.getAllWindowHandles()
|
const windowHandles = await driver.getAllWindowHandles()
|
||||||
await driver.switchTo().window(windowHandles[0])
|
await driver.switchTo().window(windowHandles[0])
|
||||||
})
|
})
|
||||||
@ -98,6 +89,7 @@ describe('Metamask popup page', function () {
|
|||||||
it('allows the button to be clicked when scrolled to the bottom of TOU', async () => {
|
it('allows the button to be clicked when scrolled to the bottom of TOU', async () => {
|
||||||
const button = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > button'))
|
const button = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > button'))
|
||||||
await button.click()
|
await button.click()
|
||||||
|
await delay(300)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows privacy notice', async () => {
|
it('shows privacy notice', async () => {
|
||||||
@ -108,7 +100,6 @@ describe('Metamask popup page', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('shows phishing notice', async () => {
|
it('shows phishing notice', async () => {
|
||||||
await delay(300)
|
|
||||||
const noticeHeader = await driver.findElement(By.css('.terms-header')).getText()
|
const noticeHeader = await driver.findElement(By.css('.terms-header')).getText()
|
||||||
assert.equal(noticeHeader, 'PHISHING WARNING', 'shows phishing warning')
|
assert.equal(noticeHeader, 'PHISHING WARNING', 'shows phishing warning')
|
||||||
const element = await driver.findElement(By.css('.markdown'))
|
const element = await driver.findElement(By.css('.markdown'))
|
||||||
@ -295,11 +286,7 @@ describe('Metamask popup page', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('navigates back to MetaMask popup in the tab', async function () {
|
it('navigates back to MetaMask popup in the tab', async function () {
|
||||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
await driver.get(extensionUri)
|
||||||
await driver.get(`chrome-extension://${extensionId}/popup.html`)
|
|
||||||
} else if (process.env.SELENIUM_BROWSER === 'firefox') {
|
|
||||||
await driver.get(`moz-extension://${extensionId}/popup.html`)
|
|
||||||
}
|
|
||||||
await delay(700)
|
await delay(700)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -362,21 +349,4 @@ describe('Metamask popup page', function () {
|
|||||||
return matchedErrorObjects
|
return matchedErrorObjects
|
||||||
}
|
}
|
||||||
|
|
||||||
async function verboseReportOnFailure (test) {
|
|
||||||
let artifactDir
|
|
||||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
|
||||||
artifactDir = `./test-artifacts/chrome/${test.title}`
|
|
||||||
} else if (process.env.SELENIUM_BROWSER === 'firefox') {
|
|
||||||
artifactDir = `./test-artifacts/firefox/${test.title}`
|
|
||||||
}
|
|
||||||
const filepathBase = `${artifactDir}/test-failure`
|
|
||||||
await pify(mkdirp)(artifactDir)
|
|
||||||
// capture screenshot
|
|
||||||
const screenshot = await driver.takeScreenshot()
|
|
||||||
await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' })
|
|
||||||
// capture dom source
|
|
||||||
const htmlSource = await driver.getPageSource()
|
|
||||||
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -1,10 +1,21 @@
|
|||||||
|
const Ganache = require('ganache-core')
|
||||||
|
const nock = require('nock')
|
||||||
import Enzyme from 'enzyme'
|
import Enzyme from 'enzyme'
|
||||||
import Adapter from 'enzyme-adapter-react-15'
|
import Adapter from 'enzyme-adapter-react-15'
|
||||||
|
|
||||||
|
nock.disableNetConnect()
|
||||||
|
nock.enableNetConnect('localhost')
|
||||||
|
|
||||||
Enzyme.configure({ adapter: new Adapter() })
|
Enzyme.configure({ adapter: new Adapter() })
|
||||||
// disallow promises from swallowing errors
|
// disallow promises from swallowing errors
|
||||||
enableFailureOnUnhandledPromiseRejection()
|
enableFailureOnUnhandledPromiseRejection()
|
||||||
|
|
||||||
|
// ganache server
|
||||||
|
const server = Ganache.server()
|
||||||
|
server.listen(8545, () => {
|
||||||
|
console.log('Ganache Testrpc is running on "http://localhost:8545"')
|
||||||
|
})
|
||||||
|
|
||||||
// logging util
|
// logging util
|
||||||
var log = require('loglevel')
|
var log = require('loglevel')
|
||||||
log.setDefaultLevel(5)
|
log.setDefaultLevel(5)
|
||||||
@ -14,6 +25,9 @@ global.log = log
|
|||||||
// polyfills
|
// polyfills
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// fetch
|
||||||
|
global.fetch = require('isomorphic-fetch')
|
||||||
|
|
||||||
// dom
|
// dom
|
||||||
require('jsdom-global')()
|
require('jsdom-global')()
|
||||||
|
|
||||||
|
16
test/lib/createTxMeta.js
Normal file
16
test/lib/createTxMeta.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper')
|
||||||
|
|
||||||
|
module.exports = createTxMeta
|
||||||
|
|
||||||
|
function createTxMeta (partialMeta) {
|
||||||
|
const txMeta = Object.assign({
|
||||||
|
status: 'unapproved',
|
||||||
|
txParams: {},
|
||||||
|
}, partialMeta)
|
||||||
|
// initialize history
|
||||||
|
txMeta.history = []
|
||||||
|
// capture initial snapshot of txMeta for history
|
||||||
|
const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
|
||||||
|
txMeta.history.push(snapshot)
|
||||||
|
return txMeta
|
||||||
|
}
|
@ -1,6 +1,3 @@
|
|||||||
// polyfill fetch
|
|
||||||
global.fetch = global.fetch || require('isomorphic-fetch')
|
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const nock = require('nock')
|
const nock = require('nock')
|
||||||
const CurrencyController = require('../../../../app/scripts/controllers/currency')
|
const CurrencyController = require('../../../../app/scripts/controllers/currency')
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
|
const nock = require('nock')
|
||||||
const sinon = require('sinon')
|
const sinon = require('sinon')
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const DetectTokensController = require('../../../../app/scripts/controllers/detect-tokens')
|
const DetectTokensController = require('../../../../app/scripts/controllers/detect-tokens')
|
||||||
@ -6,15 +7,34 @@ const NetworkController = require('../../../../app/scripts/controllers/network/n
|
|||||||
const PreferencesController = require('../../../../app/scripts/controllers/preferences')
|
const PreferencesController = require('../../../../app/scripts/controllers/preferences')
|
||||||
|
|
||||||
describe('DetectTokensController', () => {
|
describe('DetectTokensController', () => {
|
||||||
const sandbox = sinon.createSandbox()
|
const sandbox = sinon.createSandbox()
|
||||||
let clock, keyringMemStore, network, preferences
|
let clock, keyringMemStore, network, preferences, controller
|
||||||
beforeEach(async () => {
|
|
||||||
keyringMemStore = new ObservableStore({ isUnlocked: false})
|
const noop = () => {}
|
||||||
network = new NetworkController({ provider: { type: 'mainnet' }})
|
|
||||||
preferences = new PreferencesController({ network })
|
const networkControllerProviderConfig = {
|
||||||
})
|
getAccounts: noop,
|
||||||
after(() => {
|
}
|
||||||
sandbox.restore()
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
|
||||||
|
|
||||||
|
nock('https://api.infura.io')
|
||||||
|
.get(/.*/)
|
||||||
|
.reply(200)
|
||||||
|
|
||||||
|
keyringMemStore = new ObservableStore({ isUnlocked: false})
|
||||||
|
network = new NetworkController()
|
||||||
|
preferences = new PreferencesController({ network })
|
||||||
|
controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
||||||
|
|
||||||
|
network.initializeProvider(networkControllerProviderConfig)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
sandbox.restore()
|
||||||
|
nock.cleanAll()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should poll on correct interval', async () => {
|
it('should poll on correct interval', async () => {
|
||||||
@ -26,7 +46,10 @@ describe('DetectTokensController', () => {
|
|||||||
|
|
||||||
it('should be called on every polling period', async () => {
|
it('should be called on every polling period', async () => {
|
||||||
clock = sandbox.useFakeTimers()
|
clock = sandbox.useFakeTimers()
|
||||||
|
const network = new NetworkController()
|
||||||
|
network.initializeProvider(networkControllerProviderConfig)
|
||||||
network.setProviderType('mainnet')
|
network.setProviderType('mainnet')
|
||||||
|
const preferences = new PreferencesController({ network })
|
||||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
||||||
controller.isOpen = true
|
controller.isOpen = true
|
||||||
controller.isUnlocked = true
|
controller.isUnlocked = true
|
||||||
@ -44,8 +67,6 @@ describe('DetectTokensController', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should not check tokens while in test network', async () => {
|
it('should not check tokens while in test network', async () => {
|
||||||
network.setProviderType('rinkeby')
|
|
||||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
|
||||||
controller.isOpen = true
|
controller.isOpen = true
|
||||||
controller.isUnlocked = true
|
controller.isUnlocked = true
|
||||||
|
|
||||||
@ -58,7 +79,6 @@ describe('DetectTokensController', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should only check and add tokens while in main network', async () => {
|
it('should only check and add tokens while in main network', async () => {
|
||||||
network.setProviderType('mainnet')
|
|
||||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
||||||
controller.isOpen = true
|
controller.isOpen = true
|
||||||
controller.isUnlocked = true
|
controller.isUnlocked = true
|
||||||
@ -75,7 +95,6 @@ describe('DetectTokensController', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should not detect same token while in main network', async () => {
|
it('should not detect same token while in main network', async () => {
|
||||||
network.setProviderType('mainnet')
|
|
||||||
preferences.addToken('0x0d262e5dc4a06a0f1c90ce79c7a60c09dfc884e4', 'J8T', 8)
|
preferences.addToken('0x0d262e5dc4a06a0f1c90ce79c7a60c09dfc884e4', 'J8T', 8)
|
||||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
||||||
controller.isOpen = true
|
controller.isOpen = true
|
||||||
@ -93,8 +112,6 @@ describe('DetectTokensController', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should trigger detect new tokens when change address', async () => {
|
it('should trigger detect new tokens when change address', async () => {
|
||||||
network.setProviderType('mainnet')
|
|
||||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
|
||||||
controller.isOpen = true
|
controller.isOpen = true
|
||||||
controller.isUnlocked = true
|
controller.isUnlocked = true
|
||||||
var stub = sandbox.stub(controller, 'detectNewTokens')
|
var stub = sandbox.stub(controller, 'detectNewTokens')
|
||||||
@ -103,8 +120,6 @@ describe('DetectTokensController', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should trigger detect new tokens when submit password', async () => {
|
it('should trigger detect new tokens when submit password', async () => {
|
||||||
network.setProviderType('mainnet')
|
|
||||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
|
||||||
controller.isOpen = true
|
controller.isOpen = true
|
||||||
controller.selectedAddress = '0x0'
|
controller.selectedAddress = '0x0'
|
||||||
var stub = sandbox.stub(controller, 'detectNewTokens')
|
var stub = sandbox.stub(controller, 'detectNewTokens')
|
||||||
@ -113,8 +128,6 @@ describe('DetectTokensController', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should not trigger detect new tokens when not open or not unlocked', async () => {
|
it('should not trigger detect new tokens when not open or not unlocked', async () => {
|
||||||
network.setProviderType('mainnet')
|
|
||||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
|
||||||
controller.isOpen = true
|
controller.isOpen = true
|
||||||
controller.isUnlocked = false
|
controller.isUnlocked = false
|
||||||
var stub = sandbox.stub(controller, 'detectTokenBalance')
|
var stub = sandbox.stub(controller, 'detectTokenBalance')
|
||||||
@ -125,4 +138,4 @@ describe('DetectTokensController', () => {
|
|||||||
clock.tick(180000)
|
clock.tick(180000)
|
||||||
sandbox.assert.notCalled(stub)
|
sandbox.assert.notCalled(stub)
|
||||||
})
|
})
|
||||||
})
|
})
|
@ -3,16 +3,22 @@ const sinon = require('sinon')
|
|||||||
const clone = require('clone')
|
const clone = require('clone')
|
||||||
const nock = require('nock')
|
const nock = require('nock')
|
||||||
const createThoughStream = require('through2').obj
|
const createThoughStream = require('through2').obj
|
||||||
const MetaMaskController = require('../../../../app/scripts/metamask-controller')
|
|
||||||
const blacklistJSON = require('eth-phishing-detect/src/config')
|
const blacklistJSON = require('eth-phishing-detect/src/config')
|
||||||
const firstTimeState = require('../../../../app/scripts/first-time-state')
|
const MetaMaskController = require('../../../../app/scripts/metamask-controller')
|
||||||
|
const firstTimeState = require('../../../unit/localhostState')
|
||||||
|
const createTxMeta = require('../../../lib/createTxMeta')
|
||||||
|
const EthQuery = require('eth-query')
|
||||||
|
|
||||||
const currentNetworkId = 42
|
const currentNetworkId = 42
|
||||||
const DEFAULT_LABEL = 'Account 1'
|
const DEFAULT_LABEL = 'Account 1'
|
||||||
|
const DEFAULT_LABEL_2 = 'Account 2'
|
||||||
const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'
|
const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'
|
||||||
const TEST_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'
|
const TEST_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'
|
||||||
|
const TEST_ADDRESS_2 = '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b'
|
||||||
|
const TEST_ADDRESS_3 = '0xeb9e64b93097bc15f01f13eae97015c57ab64823'
|
||||||
const TEST_SEED_ALT = 'setup olympic issue mobile velvet surge alcohol burger horse view reopen gentle'
|
const TEST_SEED_ALT = 'setup olympic issue mobile velvet surge alcohol burger horse view reopen gentle'
|
||||||
const TEST_ADDRESS_ALT = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'
|
const TEST_ADDRESS_ALT = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'
|
||||||
|
const CUSTOM_RPC_URL = 'http://localhost:8545'
|
||||||
|
|
||||||
describe('MetaMaskController', function () {
|
describe('MetaMaskController', function () {
|
||||||
let metamaskController
|
let metamaskController
|
||||||
@ -134,6 +140,9 @@ describe('MetaMaskController', function () {
|
|||||||
describe('#createNewVaultAndRestore', function () {
|
describe('#createNewVaultAndRestore', function () {
|
||||||
it('should be able to call newVaultAndRestore despite a mistake.', async function () {
|
it('should be able to call newVaultAndRestore despite a mistake.', async function () {
|
||||||
const password = 'what-what-what'
|
const password = 'what-what-what'
|
||||||
|
sandbox.stub(metamaskController, 'getBalance')
|
||||||
|
metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') })
|
||||||
|
|
||||||
await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch((e) => null)
|
await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch((e) => null)
|
||||||
await metamaskController.createNewVaultAndRestore(password, TEST_SEED)
|
await metamaskController.createNewVaultAndRestore(password, TEST_SEED)
|
||||||
|
|
||||||
@ -141,6 +150,9 @@ describe('MetaMaskController', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should clear previous identities after vault restoration', async () => {
|
it('should clear previous identities after vault restoration', async () => {
|
||||||
|
sandbox.stub(metamaskController, 'getBalance')
|
||||||
|
metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') })
|
||||||
|
|
||||||
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED)
|
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED)
|
||||||
assert.deepEqual(metamaskController.getState().identities, {
|
assert.deepEqual(metamaskController.getState().identities, {
|
||||||
[TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL },
|
[TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL },
|
||||||
@ -156,6 +168,54 @@ describe('MetaMaskController', function () {
|
|||||||
[TEST_ADDRESS_ALT]: { address: TEST_ADDRESS_ALT, name: DEFAULT_LABEL },
|
[TEST_ADDRESS_ALT]: { address: TEST_ADDRESS_ALT, name: DEFAULT_LABEL },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should restore any consecutive accounts with balances', async () => {
|
||||||
|
sandbox.stub(metamaskController, 'getBalance')
|
||||||
|
metamaskController.getBalance.withArgs(TEST_ADDRESS).callsFake(() => {
|
||||||
|
return Promise.resolve('0x14ced5122ce0a000')
|
||||||
|
})
|
||||||
|
metamaskController.getBalance.withArgs(TEST_ADDRESS_2).callsFake(() => {
|
||||||
|
return Promise.resolve('0x0')
|
||||||
|
})
|
||||||
|
metamaskController.getBalance.withArgs(TEST_ADDRESS_3).callsFake(() => {
|
||||||
|
return Promise.resolve('0x14ced5122ce0a000')
|
||||||
|
})
|
||||||
|
|
||||||
|
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED)
|
||||||
|
assert.deepEqual(metamaskController.getState().identities, {
|
||||||
|
[TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL },
|
||||||
|
[TEST_ADDRESS_2]: { address: TEST_ADDRESS_2, name: DEFAULT_LABEL_2 },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getBalance', () => {
|
||||||
|
it('should return the balance known by accountTracker', async () => {
|
||||||
|
const accounts = {}
|
||||||
|
const balance = '0x14ced5122ce0a000'
|
||||||
|
accounts[TEST_ADDRESS] = { balance: balance }
|
||||||
|
|
||||||
|
metamaskController.accountTracker.store.putState({ accounts: accounts })
|
||||||
|
|
||||||
|
const gotten = await metamaskController.getBalance(TEST_ADDRESS)
|
||||||
|
|
||||||
|
assert.equal(balance, gotten)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should ask the network for a balance when not known by accountTracker', async () => {
|
||||||
|
const accounts = {}
|
||||||
|
const balance = '0x14ced5122ce0a000'
|
||||||
|
const ethQuery = new EthQuery()
|
||||||
|
sinon.stub(ethQuery, 'getBalance').callsFake((account, callback) => {
|
||||||
|
callback(undefined, balance)
|
||||||
|
})
|
||||||
|
|
||||||
|
metamaskController.accountTracker.store.putState({ accounts: accounts })
|
||||||
|
|
||||||
|
const gotten = await metamaskController.getBalance(TEST_ADDRESS, ethQuery)
|
||||||
|
|
||||||
|
assert.equal(balance, gotten)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#getApi', function () {
|
describe('#getApi', function () {
|
||||||
@ -226,9 +286,9 @@ describe('MetaMaskController', function () {
|
|||||||
|
|
||||||
it('should throw if it receives an unknown device name', async function () {
|
it('should throw if it receives an unknown device name', async function () {
|
||||||
try {
|
try {
|
||||||
await metamaskController.connectHardware('Some random device name', 0)
|
await metamaskController.connectHardware('Some random device name', 0, `m/44/0'/0'`)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
assert.equal(e, 'Error: MetamaskController:connectHardware - Unknown device')
|
assert.equal(e, 'Error: MetamaskController:getKeyringForDevice - Unknown device')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -242,14 +302,24 @@ describe('MetaMaskController', function () {
|
|||||||
assert.equal(keyrings.length, 1)
|
assert.equal(keyrings.length, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should add the Ledger Hardware keyring', async function () {
|
||||||
|
sinon.spy(metamaskController.keyringController, 'addNewKeyring')
|
||||||
|
await metamaskController.connectHardware('ledger', 0).catch((e) => null)
|
||||||
|
const keyrings = await metamaskController.keyringController.getKeyringsByType(
|
||||||
|
'Ledger Hardware'
|
||||||
|
)
|
||||||
|
assert.equal(metamaskController.keyringController.addNewKeyring.getCall(0).args, 'Ledger Hardware')
|
||||||
|
assert.equal(keyrings.length, 1)
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('checkHardwareStatus', function () {
|
describe('checkHardwareStatus', function () {
|
||||||
it('should throw if it receives an unknown device name', async function () {
|
it('should throw if it receives an unknown device name', async function () {
|
||||||
try {
|
try {
|
||||||
await metamaskController.checkHardwareStatus('Some random device name')
|
await metamaskController.checkHardwareStatus('Some random device name', `m/44/0'/0'`)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
assert.equal(e, 'Error: MetamaskController:checkHardwareStatus - Unknown device')
|
assert.equal(e, 'Error: MetamaskController:getKeyringForDevice - Unknown device')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -265,7 +335,7 @@ describe('MetaMaskController', function () {
|
|||||||
try {
|
try {
|
||||||
await metamaskController.forgetDevice('Some random device name')
|
await metamaskController.forgetDevice('Some random device name')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
assert.equal(e, 'Error: MetamaskController:forgetDevice - Unknown device')
|
assert.equal(e, 'Error: MetamaskController:getKeyringForDevice - Unknown device')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -282,7 +352,7 @@ describe('MetaMaskController', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('unlockTrezorAccount', function () {
|
describe('unlockHardwareWalletAccount', function () {
|
||||||
let accountToUnlock
|
let accountToUnlock
|
||||||
let windowOpenStub
|
let windowOpenStub
|
||||||
let addNewAccountStub
|
let addNewAccountStub
|
||||||
@ -305,16 +375,20 @@ describe('MetaMaskController', function () {
|
|||||||
sinon.spy(metamaskController.preferencesController, 'setAddresses')
|
sinon.spy(metamaskController.preferencesController, 'setAddresses')
|
||||||
sinon.spy(metamaskController.preferencesController, 'setSelectedAddress')
|
sinon.spy(metamaskController.preferencesController, 'setSelectedAddress')
|
||||||
sinon.spy(metamaskController.preferencesController, 'setAccountLabel')
|
sinon.spy(metamaskController.preferencesController, 'setAccountLabel')
|
||||||
await metamaskController.connectHardware('trezor', 0).catch((e) => null)
|
await metamaskController.connectHardware('trezor', 0, `m/44/0'/0'`).catch((e) => null)
|
||||||
await metamaskController.unlockTrezorAccount(accountToUnlock).catch((e) => null)
|
await metamaskController.unlockHardwareWalletAccount(accountToUnlock, 'trezor', `m/44/0'/0'`)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
metamaskController.keyringController.addNewAccount.restore()
|
|
||||||
window.open.restore()
|
window.open.restore()
|
||||||
|
metamaskController.keyringController.addNewAccount.restore()
|
||||||
|
metamaskController.keyringController.getAccounts.restore()
|
||||||
|
metamaskController.preferencesController.setAddresses.restore()
|
||||||
|
metamaskController.preferencesController.setSelectedAddress.restore()
|
||||||
|
metamaskController.preferencesController.setAccountLabel.restore()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should set accountToUnlock in the keyring', async function () {
|
it('should set unlockedAccount in the keyring', async function () {
|
||||||
const keyrings = await metamaskController.keyringController.getKeyringsByType(
|
const keyrings = await metamaskController.keyringController.getKeyringsByType(
|
||||||
'Trezor Hardware'
|
'Trezor Hardware'
|
||||||
)
|
)
|
||||||
@ -322,7 +396,7 @@ describe('MetaMaskController', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
it('should call keyringController.addNewAccount', async function () {
|
it('should call keyringController.addNewAccount', async function () {
|
||||||
assert(metamaskController.keyringController.addNewAccount.calledOnce)
|
assert(metamaskController.keyringController.addNewAccount.calledOnce)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -346,29 +420,19 @@ describe('MetaMaskController', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('#setCustomRpc', function () {
|
describe('#setCustomRpc', function () {
|
||||||
const customRPC = 'https://custom.rpc/'
|
|
||||||
let rpcTarget
|
let rpcTarget
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
rpcTarget = metamaskController.setCustomRpc(CUSTOM_RPC_URL)
|
||||||
nock('https://custom.rpc')
|
|
||||||
.post('/')
|
|
||||||
.reply(200)
|
|
||||||
|
|
||||||
rpcTarget = metamaskController.setCustomRpc(customRPC)
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
nock.cleanAll()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns custom RPC that when called', async function () {
|
it('returns custom RPC that when called', async function () {
|
||||||
assert.equal(await rpcTarget, customRPC)
|
assert.equal(await rpcTarget, CUSTOM_RPC_URL)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('changes the network controller rpc', function () {
|
it('changes the network controller rpc', function () {
|
||||||
const networkControllerState = metamaskController.networkController.store.getState()
|
const networkControllerState = metamaskController.networkController.store.getState()
|
||||||
assert.equal(networkControllerState.provider.rpcTarget, customRPC)
|
assert.equal(networkControllerState.provider.rpcTarget, CUSTOM_RPC_URL)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -473,9 +537,10 @@ describe('MetaMaskController', function () {
|
|||||||
getNetworkstub.returns(42)
|
getNetworkstub.returns(42)
|
||||||
|
|
||||||
metamaskController.txController.txStateManager._saveTxList([
|
metamaskController.txController.txStateManager._saveTxList([
|
||||||
{ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} },
|
createTxMeta({ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} }),
|
||||||
{ id: 2, status: 'rejected', metamaskNetworkId: 32, txParams: {} },
|
createTxMeta({ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} }),
|
||||||
{ id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4'} },
|
createTxMeta({ id: 2, status: 'rejected', metamaskNetworkId: 32 }),
|
||||||
|
createTxMeta({ id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4'} }),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -552,14 +617,16 @@ describe('MetaMaskController', function () {
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#newUnsignedMessage', function () {
|
describe('#newUnsignedMessage', () => {
|
||||||
|
|
||||||
let msgParams, metamaskMsgs, messages, msgId
|
let msgParams, metamaskMsgs, messages, msgId
|
||||||
|
|
||||||
const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'
|
const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'
|
||||||
const data = '0x43727970746f6b697474696573'
|
const data = '0x43727970746f6b697474696573'
|
||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async () => {
|
||||||
|
sandbox.stub(metamaskController, 'getBalance')
|
||||||
|
metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') })
|
||||||
|
|
||||||
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT)
|
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT)
|
||||||
|
|
||||||
@ -568,7 +635,10 @@ describe('MetaMaskController', function () {
|
|||||||
'data': data,
|
'data': data,
|
||||||
}
|
}
|
||||||
|
|
||||||
metamaskController.newUnsignedMessage(msgParams, noop)
|
const promise = metamaskController.newUnsignedMessage(msgParams)
|
||||||
|
// handle the promise so it doesn't throw an unhandledRejection
|
||||||
|
promise.then(noop).catch(noop)
|
||||||
|
|
||||||
metamaskMsgs = metamaskController.messageManager.getUnapprovedMsgs()
|
metamaskMsgs = metamaskController.messageManager.getUnapprovedMsgs()
|
||||||
messages = metamaskController.messageManager.messages
|
messages = metamaskController.messageManager.messages
|
||||||
msgId = Object.keys(metamaskMsgs)[0]
|
msgId = Object.keys(metamaskMsgs)[0]
|
||||||
@ -608,13 +678,16 @@ describe('MetaMaskController', function () {
|
|||||||
|
|
||||||
describe('#newUnsignedPersonalMessage', function () {
|
describe('#newUnsignedPersonalMessage', function () {
|
||||||
|
|
||||||
it('errors with no from in msgParams', function () {
|
it('errors with no from in msgParams', async () => {
|
||||||
const msgParams = {
|
const msgParams = {
|
||||||
'data': data,
|
'data': data,
|
||||||
}
|
}
|
||||||
metamaskController.newUnsignedPersonalMessage(msgParams, function (error) {
|
try {
|
||||||
|
await metamaskController.newUnsignedPersonalMessage(msgParams)
|
||||||
|
assert.fail('should have thrown')
|
||||||
|
} catch (error) {
|
||||||
assert.equal(error.message, 'MetaMask Message Signature: from field is required.')
|
assert.equal(error.message, 'MetaMask Message Signature: from field is required.')
|
||||||
})
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let msgParams, metamaskPersonalMsgs, personalMessages, msgId
|
let msgParams, metamaskPersonalMsgs, personalMessages, msgId
|
||||||
@ -623,6 +696,8 @@ describe('MetaMaskController', function () {
|
|||||||
const data = '0x43727970746f6b697474696573'
|
const data = '0x43727970746f6b697474696573'
|
||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
|
sandbox.stub(metamaskController, 'getBalance')
|
||||||
|
metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') })
|
||||||
|
|
||||||
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT)
|
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT)
|
||||||
|
|
||||||
@ -631,7 +706,10 @@ describe('MetaMaskController', function () {
|
|||||||
'data': data,
|
'data': data,
|
||||||
}
|
}
|
||||||
|
|
||||||
metamaskController.newUnsignedPersonalMessage(msgParams, noop)
|
const promise = metamaskController.newUnsignedPersonalMessage(msgParams)
|
||||||
|
// handle the promise so it doesn't throw an unhandledRejection
|
||||||
|
promise.then(noop).catch(noop)
|
||||||
|
|
||||||
metamaskPersonalMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs()
|
metamaskPersonalMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs()
|
||||||
personalMessages = metamaskController.personalMessageManager.messages
|
personalMessages = metamaskController.personalMessageManager.messages
|
||||||
msgId = Object.keys(metamaskPersonalMsgs)[0]
|
msgId = Object.keys(metamaskPersonalMsgs)[0]
|
||||||
@ -670,22 +748,27 @@ describe('MetaMaskController', function () {
|
|||||||
describe('#setupUntrustedCommunication', function () {
|
describe('#setupUntrustedCommunication', function () {
|
||||||
let streamTest
|
let streamTest
|
||||||
|
|
||||||
const phishingUrl = 'decentral.market'
|
const phishingUrl = 'myethereumwalletntw.com'
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
streamTest.end()
|
streamTest.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sets up phishing stream for untrusted communication ', async function () {
|
it('sets up phishing stream for untrusted communication ', async () => {
|
||||||
await metamaskController.blacklistController.updatePhishingList()
|
await metamaskController.blacklistController.updatePhishingList()
|
||||||
|
console.log(blacklistJSON.blacklist.includes(phishingUrl))
|
||||||
|
|
||||||
|
const { promise, resolve } = deferredPromise()
|
||||||
|
|
||||||
streamTest = createThoughStream((chunk, enc, cb) => {
|
streamTest = createThoughStream((chunk, enc, cb) => {
|
||||||
assert.equal(chunk.name, 'phishing')
|
if (chunk.name !== 'phishing') return cb()
|
||||||
assert.equal(chunk.data.hostname, phishingUrl)
|
assert.equal(chunk.data.hostname, phishingUrl)
|
||||||
cb()
|
resolve()
|
||||||
})
|
cb()
|
||||||
// console.log(streamTest)
|
})
|
||||||
metamaskController.setupUntrustedCommunication(streamTest, phishingUrl)
|
metamaskController.setupUntrustedCommunication(streamTest, phishingUrl)
|
||||||
|
|
||||||
|
await promise
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -732,3 +815,9 @@ describe('MetaMaskController', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function deferredPromise () {
|
||||||
|
let resolve
|
||||||
|
const promise = new Promise(_resolve => { resolve = _resolve })
|
||||||
|
return { promise, resolve }
|
||||||
|
}
|
||||||
|
@ -32,9 +32,10 @@ describe('# Network Controller', function () {
|
|||||||
describe('#provider', function () {
|
describe('#provider', function () {
|
||||||
it('provider should be updatable without reassignment', function () {
|
it('provider should be updatable without reassignment', function () {
|
||||||
networkController.initializeProvider(networkControllerProviderConfig)
|
networkController.initializeProvider(networkControllerProviderConfig)
|
||||||
const proxy = networkController._proxy
|
const providerProxy = networkController.getProviderAndBlockTracker().provider
|
||||||
proxy.setTarget({ test: true, on: () => {} })
|
assert.equal(providerProxy.test, undefined)
|
||||||
assert.ok(proxy.test)
|
providerProxy.setTarget({ test: true })
|
||||||
|
assert.equal(providerProxy.test, true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('#getNetworkState', function () {
|
describe('#getNetworkState', function () {
|
||||||
|
@ -224,14 +224,15 @@ function generateNonceTrackerWith (pending, confirmed, providerStub = '0x0') {
|
|||||||
providerResultStub.result = providerStub
|
providerResultStub.result = providerStub
|
||||||
const provider = {
|
const provider = {
|
||||||
sendAsync: (_, cb) => { cb(undefined, providerResultStub) },
|
sendAsync: (_, cb) => { cb(undefined, providerResultStub) },
|
||||||
_blockTracker: {
|
}
|
||||||
getCurrentBlock: () => '0x11b568',
|
const blockTracker = {
|
||||||
},
|
getCurrentBlock: () => '0x11b568',
|
||||||
|
getLatestBlock: async () => '0x11b568',
|
||||||
}
|
}
|
||||||
return new NonceTracker({
|
return new NonceTracker({
|
||||||
provider,
|
provider,
|
||||||
|
blockTracker,
|
||||||
getPendingTransactions,
|
getPendingTransactions,
|
||||||
getConfirmedTransactions,
|
getConfirmedTransactions,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ describe('PendingTransactionTracker', function () {
|
|||||||
let pendingTxTracker, txMeta, txMetaNoHash, providerResultStub,
|
let pendingTxTracker, txMeta, txMetaNoHash, providerResultStub,
|
||||||
provider, txMeta3, txList, knownErrors
|
provider, txMeta3, txList, knownErrors
|
||||||
this.timeout(10000)
|
this.timeout(10000)
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
txMeta = {
|
txMeta = {
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -40,7 +41,10 @@ describe('PendingTransactionTracker', function () {
|
|||||||
getPendingTransactions: () => { return [] },
|
getPendingTransactions: () => { return [] },
|
||||||
getCompletedTransactions: () => { return [] },
|
getCompletedTransactions: () => { return [] },
|
||||||
publishTransaction: () => {},
|
publishTransaction: () => {},
|
||||||
|
confirmTransaction: () => {},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
pendingTxTracker._getBlock = (blockNumber) => { return {number: blockNumber, transactions: []} }
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('_checkPendingTx state management', function () {
|
describe('_checkPendingTx state management', function () {
|
||||||
@ -92,58 +96,6 @@ describe('PendingTransactionTracker', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#checkForTxInBlock', function () {
|
|
||||||
it('should return if no pending transactions', function () {
|
|
||||||
// throw a type error if it trys to do anything on the block
|
|
||||||
// thus failing the test
|
|
||||||
const block = Proxy.revocable({}, {}).revoke()
|
|
||||||
pendingTxTracker.checkForTxInBlock(block)
|
|
||||||
})
|
|
||||||
it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) {
|
|
||||||
const block = Proxy.revocable({}, {}).revoke()
|
|
||||||
pendingTxTracker.getPendingTransactions = () => [txMetaNoHash]
|
|
||||||
pendingTxTracker.once('tx:failed', (txId, err) => {
|
|
||||||
assert(txId, txMetaNoHash.id, 'should pass txId')
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
pendingTxTracker.checkForTxInBlock(block)
|
|
||||||
})
|
|
||||||
it('should emit \'txConfirmed\' if the tx is in the block', function (done) {
|
|
||||||
const block = { transactions: [txMeta]}
|
|
||||||
pendingTxTracker.getPendingTransactions = () => [txMeta]
|
|
||||||
pendingTxTracker.once('tx:confirmed', (txId) => {
|
|
||||||
assert(txId, txMeta.id, 'should pass txId')
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
pendingTxTracker.once('tx:failed', (_, err) => { done(err) })
|
|
||||||
pendingTxTracker.checkForTxInBlock(block)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe('#queryPendingTxs', function () {
|
|
||||||
it('should call #_checkPendingTxs if their is no oldBlock', function (done) {
|
|
||||||
let oldBlock
|
|
||||||
const newBlock = { number: '0x01' }
|
|
||||||
pendingTxTracker._checkPendingTxs = done
|
|
||||||
pendingTxTracker.queryPendingTxs({ oldBlock, newBlock })
|
|
||||||
})
|
|
||||||
it('should call #_checkPendingTxs if oldBlock and the newBlock have a diff of greater then 1', function (done) {
|
|
||||||
const oldBlock = { number: '0x01' }
|
|
||||||
const newBlock = { number: '0x03' }
|
|
||||||
pendingTxTracker._checkPendingTxs = done
|
|
||||||
pendingTxTracker.queryPendingTxs({ oldBlock, newBlock })
|
|
||||||
})
|
|
||||||
it('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less', function (done) {
|
|
||||||
const oldBlock = { number: '0x1' }
|
|
||||||
const newBlock = { number: '0x2' }
|
|
||||||
pendingTxTracker._checkPendingTxs = () => {
|
|
||||||
const err = new Error('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less')
|
|
||||||
done(err)
|
|
||||||
}
|
|
||||||
pendingTxTracker.queryPendingTxs({ oldBlock, newBlock })
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#_checkPendingTx', function () {
|
describe('#_checkPendingTx', function () {
|
||||||
it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) {
|
it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) {
|
||||||
pendingTxTracker.once('tx:failed', (txId, err) => {
|
pendingTxTracker.once('tx:failed', (txId, err) => {
|
||||||
@ -157,16 +109,6 @@ describe('PendingTransactionTracker', function () {
|
|||||||
providerResultStub.eth_getTransactionByHash = null
|
providerResultStub.eth_getTransactionByHash = null
|
||||||
pendingTxTracker._checkPendingTx(txMeta)
|
pendingTxTracker._checkPendingTx(txMeta)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should emit \'txConfirmed\'', function (done) {
|
|
||||||
providerResultStub.eth_getTransactionByHash = {blockNumber: '0x01'}
|
|
||||||
pendingTxTracker.once('tx:confirmed', (txId) => {
|
|
||||||
assert(txId, txMeta.id, 'should pass txId')
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
pendingTxTracker.once('tx:failed', (_, err) => { done(err) })
|
|
||||||
pendingTxTracker._checkPendingTx(txMeta)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#_checkPendingTxs', function () {
|
describe('#_checkPendingTxs', function () {
|
||||||
@ -180,19 +122,19 @@ describe('PendingTransactionTracker', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should warp all txMeta\'s in #_checkPendingTx', function (done) {
|
it('should warp all txMeta\'s in #updatePendingTxs', function (done) {
|
||||||
pendingTxTracker.getPendingTransactions = () => txList
|
pendingTxTracker.getPendingTransactions = () => txList
|
||||||
pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) }
|
pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) }
|
||||||
Promise.all(txList.map((tx) => tx.processed))
|
Promise.all(txList.map((tx) => tx.processed))
|
||||||
.then((txCompletedList) => done())
|
.then((txCompletedList) => done())
|
||||||
.catch(done)
|
.catch(done)
|
||||||
|
|
||||||
pendingTxTracker._checkPendingTxs()
|
pendingTxTracker.updatePendingTxs()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#resubmitPendingTxs', function () {
|
describe('#resubmitPendingTxs', function () {
|
||||||
const blockStub = { number: '0x0' }
|
const blockNumberStub = '0x0'
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
const txMeta2 = txMeta3 = txMeta
|
const txMeta2 = txMeta3 = txMeta
|
||||||
txList = [txMeta, txMeta2, txMeta3].map((tx) => {
|
txList = [txMeta, txMeta2, txMeta3].map((tx) => {
|
||||||
@ -210,7 +152,7 @@ describe('PendingTransactionTracker', function () {
|
|||||||
Promise.all(txList.map((tx) => tx.processed))
|
Promise.all(txList.map((tx) => tx.processed))
|
||||||
.then((txCompletedList) => done())
|
.then((txCompletedList) => done())
|
||||||
.catch(done)
|
.catch(done)
|
||||||
pendingTxTracker.resubmitPendingTxs(blockStub)
|
pendingTxTracker.resubmitPendingTxs(blockNumberStub)
|
||||||
})
|
})
|
||||||
it('should not emit \'tx:failed\' if the txMeta throws a known txError', function (done) {
|
it('should not emit \'tx:failed\' if the txMeta throws a known txError', function (done) {
|
||||||
knownErrors = [
|
knownErrors = [
|
||||||
@ -237,7 +179,7 @@ describe('PendingTransactionTracker', function () {
|
|||||||
.then((txCompletedList) => done())
|
.then((txCompletedList) => done())
|
||||||
.catch(done)
|
.catch(done)
|
||||||
|
|
||||||
pendingTxTracker.resubmitPendingTxs(blockStub)
|
pendingTxTracker.resubmitPendingTxs(blockNumberStub)
|
||||||
})
|
})
|
||||||
it('should emit \'tx:warning\' if it encountered a real error', function (done) {
|
it('should emit \'tx:warning\' if it encountered a real error', function (done) {
|
||||||
pendingTxTracker.once('tx:warning', (txMeta, err) => {
|
pendingTxTracker.once('tx:warning', (txMeta, err) => {
|
||||||
@ -255,7 +197,7 @@ describe('PendingTransactionTracker', function () {
|
|||||||
.then((txCompletedList) => done())
|
.then((txCompletedList) => done())
|
||||||
.catch(done)
|
.catch(done)
|
||||||
|
|
||||||
pendingTxTracker.resubmitPendingTxs(blockStub)
|
pendingTxTracker.resubmitPendingTxs(blockNumberStub)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('#_resubmitTx', function () {
|
describe('#_resubmitTx', function () {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
|
const EventEmitter = require('events')
|
||||||
const ethUtil = require('ethereumjs-util')
|
const ethUtil = require('ethereumjs-util')
|
||||||
const EthTx = require('ethereumjs-tx')
|
const EthTx = require('ethereumjs-tx')
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
@ -22,12 +23,14 @@ describe('Transaction Controller', function () {
|
|||||||
}
|
}
|
||||||
provider = createTestProviderTools({ scaffold: providerResultStub }).provider
|
provider = createTestProviderTools({ scaffold: providerResultStub }).provider
|
||||||
fromAccount = getTestAccounts()[0]
|
fromAccount = getTestAccounts()[0]
|
||||||
|
const blockTrackerStub = new EventEmitter()
|
||||||
|
blockTrackerStub.getCurrentBlock = noop
|
||||||
|
blockTrackerStub.getLatestBlock = noop
|
||||||
txController = new TransactionController({
|
txController = new TransactionController({
|
||||||
provider,
|
provider,
|
||||||
networkStore: new ObservableStore(currentNetworkId),
|
networkStore: new ObservableStore(currentNetworkId),
|
||||||
txHistoryLimit: 10,
|
txHistoryLimit: 10,
|
||||||
blockTracker: { getCurrentBlock: noop, on: noop, once: noop },
|
blockTracker: blockTrackerStub,
|
||||||
signTransaction: (ethTx) => new Promise((resolve) => {
|
signTransaction: (ethTx) => new Promise((resolve) => {
|
||||||
ethTx.sign(fromAccount.key)
|
ethTx.sign(fromAccount.key)
|
||||||
resolve()
|
resolve()
|
||||||
@ -49,9 +52,9 @@ describe('Transaction Controller', function () {
|
|||||||
describe('#getUnapprovedTxCount', function () {
|
describe('#getUnapprovedTxCount', function () {
|
||||||
it('should return the number of unapproved txs', function () {
|
it('should return the number of unapproved txs', function () {
|
||||||
txController.txStateManager._saveTxList([
|
txController.txStateManager._saveTxList([
|
||||||
{ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} },
|
{ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] },
|
||||||
{ id: 2, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} },
|
{ id: 2, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] },
|
||||||
{ id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} },
|
{ id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] },
|
||||||
])
|
])
|
||||||
const unapprovedTxCount = txController.getUnapprovedTxCount()
|
const unapprovedTxCount = txController.getUnapprovedTxCount()
|
||||||
assert.equal(unapprovedTxCount, 3, 'should be 3')
|
assert.equal(unapprovedTxCount, 3, 'should be 3')
|
||||||
@ -61,9 +64,9 @@ describe('Transaction Controller', function () {
|
|||||||
describe('#getPendingTxCount', function () {
|
describe('#getPendingTxCount', function () {
|
||||||
it('should return the number of pending txs', function () {
|
it('should return the number of pending txs', function () {
|
||||||
txController.txStateManager._saveTxList([
|
txController.txStateManager._saveTxList([
|
||||||
{ id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} },
|
{ id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] },
|
||||||
{ id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} },
|
{ id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] },
|
||||||
{ id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} },
|
{ id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] },
|
||||||
])
|
])
|
||||||
const pendingTxCount = txController.getPendingTxCount()
|
const pendingTxCount = txController.getPendingTxCount()
|
||||||
assert.equal(pendingTxCount, 3, 'should be 3')
|
assert.equal(pendingTxCount, 3, 'should be 3')
|
||||||
@ -79,15 +82,15 @@ describe('Transaction Controller', function () {
|
|||||||
'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
||||||
}
|
}
|
||||||
txController.txStateManager._saveTxList([
|
txController.txStateManager._saveTxList([
|
||||||
{id: 0, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 0, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
{id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
{id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
{id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
{id: 4, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 4, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
{id: 5, status: 'approved', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 5, status: 'approved', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
{id: 6, status: 'signed', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 6, status: 'signed', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
{id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
{id: 8, status: 'failed', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 8, status: 'failed', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -201,24 +204,22 @@ describe('Transaction Controller', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('#addTxGasDefaults', function () {
|
describe('#addTxGasDefaults', function () {
|
||||||
it('should add the tx defaults if their are none', function (done) {
|
it('should add the tx defaults if their are none', async () => {
|
||||||
const txMeta = {
|
const txMeta = {
|
||||||
'txParams': {
|
txParams: {
|
||||||
'from': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
||||||
'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
to: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
||||||
},
|
},
|
||||||
|
history: [],
|
||||||
}
|
}
|
||||||
providerResultStub.eth_gasPrice = '4a817c800'
|
providerResultStub.eth_gasPrice = '4a817c800'
|
||||||
providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' }
|
providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' }
|
||||||
providerResultStub.eth_estimateGas = '5209'
|
providerResultStub.eth_estimateGas = '5209'
|
||||||
txController.addTxGasDefaults(txMeta)
|
|
||||||
.then((txMetaWithDefaults) => {
|
const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta)
|
||||||
assert(txMetaWithDefaults.txParams.value, '0x0', 'should have added 0x0 as the value')
|
assert(txMetaWithDefaults.txParams.value, '0x0', 'should have added 0x0 as the value')
|
||||||
assert(txMetaWithDefaults.txParams.gasPrice, 'should have added the gas price')
|
assert(txMetaWithDefaults.txParams.gasPrice, 'should have added the gas price')
|
||||||
assert(txMetaWithDefaults.txParams.gas, 'should have added the gas field')
|
assert(txMetaWithDefaults.txParams.gas, 'should have added the gas field')
|
||||||
done()
|
|
||||||
})
|
|
||||||
.catch(done)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -381,8 +382,9 @@ describe('Transaction Controller', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should publish a tx, updates the rawTx when provided a one', async function () {
|
it('should publish a tx, updates the rawTx when provided a one', async function () {
|
||||||
|
const rawTx = '0x477b2e6553c917af0db0388ae3da62965ff1a184558f61b749d1266b2e6d024c'
|
||||||
txController.txStateManager.addTx(txMeta)
|
txController.txStateManager.addTx(txMeta)
|
||||||
await txController.publishTransaction(txMeta.id)
|
await txController.publishTransaction(txMeta.id, rawTx)
|
||||||
const publishedTx = txController.txStateManager.getTx(1)
|
const publishedTx = txController.txStateManager.getTx(1)
|
||||||
assert.equal(publishedTx.hash, hash)
|
assert.equal(publishedTx.hash, hash)
|
||||||
assert.equal(publishedTx.status, 'submitted')
|
assert.equal(publishedTx.status, 'submitted')
|
||||||
@ -398,7 +400,7 @@ describe('Transaction Controller', function () {
|
|||||||
data: '0x0',
|
data: '0x0',
|
||||||
}
|
}
|
||||||
txController.txStateManager._saveTxList([
|
txController.txStateManager._saveTxList([
|
||||||
{ id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams },
|
{ id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
])
|
])
|
||||||
txController.retryTransaction(1)
|
txController.retryTransaction(1)
|
||||||
.then((txMeta) => {
|
.then((txMeta) => {
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
// polyfill fetch
|
|
||||||
global.fetch = global.fetch || require('isomorphic-fetch')
|
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const configManagerGen = require('../lib/mock-config-manager')
|
const configManagerGen = require('../lib/mock-config-manager')
|
||||||
|
|
||||||
|
21
test/unit/localhostState.js
Normal file
21
test/unit/localhostState.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} FirstTimeState
|
||||||
|
* @property {Object} config Initial configuration parameters
|
||||||
|
* @property {Object} NetworkController Network controller state
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {FirstTimeState}
|
||||||
|
*/
|
||||||
|
const initialState = {
|
||||||
|
config: {},
|
||||||
|
NetworkController: {
|
||||||
|
provider: {
|
||||||
|
type: 'rpc',
|
||||||
|
rpcTarget: 'http://localhost:8545',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = initialState
|
@ -12,6 +12,7 @@ const { fetchLocale } = require('../i18n-helper')
|
|||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../app/scripts/lib/enums')
|
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../app/scripts/lib/enums')
|
||||||
const { hasUnconfirmedTransactions } = require('./helpers/confirm-transaction/util')
|
const { hasUnconfirmedTransactions } = require('./helpers/confirm-transaction/util')
|
||||||
|
const WebcamUtils = require('../lib/webcam-utils')
|
||||||
|
|
||||||
var actions = {
|
var actions = {
|
||||||
_setBackgroundConnection: _setBackgroundConnection,
|
_setBackgroundConnection: _setBackgroundConnection,
|
||||||
@ -33,6 +34,8 @@ var actions = {
|
|||||||
ALERT_CLOSE: 'UI_ALERT_CLOSE',
|
ALERT_CLOSE: 'UI_ALERT_CLOSE',
|
||||||
showAlert: showAlert,
|
showAlert: showAlert,
|
||||||
hideAlert: hideAlert,
|
hideAlert: hideAlert,
|
||||||
|
QR_CODE_DETECTED: 'UI_QR_CODE_DETECTED',
|
||||||
|
qrCodeDetected,
|
||||||
// network dropdown open
|
// network dropdown open
|
||||||
NETWORK_DROPDOWN_OPEN: 'UI_NETWORK_DROPDOWN_OPEN',
|
NETWORK_DROPDOWN_OPEN: 'UI_NETWORK_DROPDOWN_OPEN',
|
||||||
NETWORK_DROPDOWN_CLOSE: 'UI_NETWORK_DROPDOWN_CLOSE',
|
NETWORK_DROPDOWN_CLOSE: 'UI_NETWORK_DROPDOWN_CLOSE',
|
||||||
@ -88,7 +91,7 @@ var actions = {
|
|||||||
connectHardware,
|
connectHardware,
|
||||||
checkHardwareStatus,
|
checkHardwareStatus,
|
||||||
forgetDevice,
|
forgetDevice,
|
||||||
unlockTrezorAccount,
|
unlockHardwareWalletAccount,
|
||||||
NEW_ACCOUNT_SCREEN: 'NEW_ACCOUNT_SCREEN',
|
NEW_ACCOUNT_SCREEN: 'NEW_ACCOUNT_SCREEN',
|
||||||
navigateToNewAccountScreen,
|
navigateToNewAccountScreen,
|
||||||
resetAccount,
|
resetAccount,
|
||||||
@ -125,7 +128,8 @@ var actions = {
|
|||||||
SHOW_CONF_TX_PAGE: 'SHOW_CONF_TX_PAGE',
|
SHOW_CONF_TX_PAGE: 'SHOW_CONF_TX_PAGE',
|
||||||
SHOW_CONF_MSG_PAGE: 'SHOW_CONF_MSG_PAGE',
|
SHOW_CONF_MSG_PAGE: 'SHOW_CONF_MSG_PAGE',
|
||||||
SET_CURRENT_FIAT: 'SET_CURRENT_FIAT',
|
SET_CURRENT_FIAT: 'SET_CURRENT_FIAT',
|
||||||
setCurrentCurrency: setCurrentCurrency,
|
showQrScanner,
|
||||||
|
setCurrentCurrency,
|
||||||
setCurrentAccountTab,
|
setCurrentAccountTab,
|
||||||
// account detail screen
|
// account detail screen
|
||||||
SHOW_SEND_PAGE: 'SHOW_SEND_PAGE',
|
SHOW_SEND_PAGE: 'SHOW_SEND_PAGE',
|
||||||
@ -231,6 +235,8 @@ var actions = {
|
|||||||
UPDATE_TOKENS: 'UPDATE_TOKENS',
|
UPDATE_TOKENS: 'UPDATE_TOKENS',
|
||||||
setRpcTarget: setRpcTarget,
|
setRpcTarget: setRpcTarget,
|
||||||
setProviderType: setProviderType,
|
setProviderType: setProviderType,
|
||||||
|
SET_HARDWARE_WALLET_DEFAULT_HD_PATH: 'SET_HARDWARE_WALLET_DEFAULT_HD_PATH',
|
||||||
|
setHardwareWalletDefaultHdPath,
|
||||||
updateProviderType,
|
updateProviderType,
|
||||||
// loading overlay
|
// loading overlay
|
||||||
SHOW_LOADING: 'SHOW_LOADING_INDICATION',
|
SHOW_LOADING: 'SHOW_LOADING_INDICATION',
|
||||||
@ -635,12 +641,12 @@ function addNewAccount () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkHardwareStatus (deviceName) {
|
function checkHardwareStatus (deviceName, hdPath) {
|
||||||
log.debug(`background.checkHardwareStatus`, deviceName)
|
log.debug(`background.checkHardwareStatus`, deviceName, hdPath)
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(actions.showLoadingIndication())
|
dispatch(actions.showLoadingIndication())
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
background.checkHardwareStatus(deviceName, (err, unlocked) => {
|
background.checkHardwareStatus(deviceName, hdPath, (err, unlocked) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
log.error(err)
|
log.error(err)
|
||||||
dispatch(actions.displayWarning(err.message))
|
dispatch(actions.displayWarning(err.message))
|
||||||
@ -677,12 +683,12 @@ function forgetDevice (deviceName) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectHardware (deviceName, page) {
|
function connectHardware (deviceName, page, hdPath) {
|
||||||
log.debug(`background.connectHardware`, deviceName, page)
|
log.debug(`background.connectHardware`, deviceName, page, hdPath)
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(actions.showLoadingIndication())
|
dispatch(actions.showLoadingIndication())
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
background.connectHardware(deviceName, page, (err, accounts) => {
|
background.connectHardware(deviceName, page, hdPath, (err, accounts) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
log.error(err)
|
log.error(err)
|
||||||
dispatch(actions.displayWarning(err.message))
|
dispatch(actions.displayWarning(err.message))
|
||||||
@ -698,12 +704,12 @@ function connectHardware (deviceName, page) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function unlockTrezorAccount (index) {
|
function unlockHardwareWalletAccount (index, deviceName, hdPath) {
|
||||||
log.debug(`background.unlockTrezorAccount`, index)
|
log.debug(`background.unlockHardwareWalletAccount`, index, deviceName, hdPath)
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(actions.showLoadingIndication())
|
dispatch(actions.showLoadingIndication())
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
background.unlockTrezorAccount(index, (err, accounts) => {
|
background.unlockHardwareWalletAccount(index, deviceName, hdPath, (err, accounts) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
log.error(err)
|
log.error(err)
|
||||||
dispatch(actions.displayWarning(err.message))
|
dispatch(actions.displayWarning(err.message))
|
||||||
@ -723,6 +729,28 @@ function showInfoPage () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showQrScanner (ROUTE) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
return WebcamUtils.checkStatus()
|
||||||
|
.then(status => {
|
||||||
|
if (!status.environmentReady) {
|
||||||
|
// We need to switch to fullscreen mode to ask for permission
|
||||||
|
global.platform.openExtensionInBrowser(`${ROUTE}`, `scan=true`)
|
||||||
|
} else {
|
||||||
|
dispatch(actions.showModal({
|
||||||
|
name: 'QR_SCANNER',
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
dispatch(actions.showModal({
|
||||||
|
name: 'QR_SCANNER',
|
||||||
|
error: true,
|
||||||
|
errorType: e.type,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setCurrentCurrency (currencyCode) {
|
function setCurrentCurrency (currencyCode) {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
dispatch(actions.showLoadingIndication())
|
dispatch(actions.showLoadingIndication())
|
||||||
@ -1809,6 +1837,17 @@ function hideAlert () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This action will receive two types of values via qrCodeData
|
||||||
|
* an object with the following structure {type, values}
|
||||||
|
* or null (used to clear the previous value)
|
||||||
|
*/
|
||||||
|
function qrCodeDetected (qrCodeData) {
|
||||||
|
return {
|
||||||
|
type: actions.QR_CODE_DETECTED,
|
||||||
|
value: qrCodeData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function showLoadingIndication (message) {
|
function showLoadingIndication (message) {
|
||||||
return {
|
return {
|
||||||
@ -1817,6 +1856,13 @@ function showLoadingIndication (message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setHardwareWalletDefaultHdPath ({ device, path }) {
|
||||||
|
return {
|
||||||
|
type: actions.SET_HARDWARE_WALLET_DEFAULT_HD_PATH,
|
||||||
|
value: {device, path},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function hideLoadingIndication () {
|
function hideLoadingIndication () {
|
||||||
return {
|
return {
|
||||||
type: actions.HIDE_LOADING,
|
type: actions.HIDE_LOADING,
|
||||||
|
@ -39,8 +39,7 @@ const Modal = require('./components/modals/index').Modal
|
|||||||
// Global Alert
|
// Global Alert
|
||||||
const Alert = require('./components/alert')
|
const Alert = require('./components/alert')
|
||||||
|
|
||||||
const AppHeader = require('./components/app-header')
|
import AppHeader from './components/app-header'
|
||||||
|
|
||||||
import UnlockPage from './components/pages/unlock-page'
|
import UnlockPage from './components/pages/unlock-page'
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
|
@ -229,6 +229,7 @@ AccountMenu.prototype.renderKeyringType = function (keyring) {
|
|||||||
let label
|
let label
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'Trezor Hardware':
|
case 'Trezor Hardware':
|
||||||
|
case 'Ledger Hardware':
|
||||||
label = this.context.t('hardware')
|
label = this.context.t('hardware')
|
||||||
break
|
break
|
||||||
case 'Simple Key Pair':
|
case 'Simple Key Pair':
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { matchPath } from 'react-router-dom'
|
import { matchPath } from 'react-router-dom'
|
||||||
@ -11,7 +11,7 @@ const { DEFAULT_ROUTE, INITIALIZE_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('
|
|||||||
const Identicon = require('../identicon')
|
const Identicon = require('../identicon')
|
||||||
const NetworkIndicator = require('../network')
|
const NetworkIndicator = require('../network')
|
||||||
|
|
||||||
class AppHeader extends Component {
|
export default class AppHeader extends PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
history: PropTypes.object,
|
history: PropTypes.object,
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
@ -107,20 +107,19 @@ class AppHeader extends Component {
|
|||||||
onClick={() => history.push(DEFAULT_ROUTE)}
|
onClick={() => history.push(DEFAULT_ROUTE)}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
className="app-header__metafox"
|
className="app-header__metafox-logo app-header__metafox-logo--horizontal"
|
||||||
src="/images/metamask-fox.svg"
|
src="/images/logo/metamask-logo-horizontal-beta.svg"
|
||||||
|
height={30}
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
className="app-header__metafox-logo app-header__metafox-logo--icon"
|
||||||
|
src="/images/logo/metamask-fox.svg"
|
||||||
height={42}
|
height={42}
|
||||||
width={42}
|
width={42}
|
||||||
/>
|
/>
|
||||||
<div className="flex-row">
|
|
||||||
<h1>{ this.context.t('appName') }</h1>
|
|
||||||
<div className="app-header__beta-label">
|
|
||||||
{ this.context.t('beta') }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="app-header__account-menu-container">
|
<div className="app-header__account-menu-container">
|
||||||
<div className="network-component-wrapper">
|
<div className="app-header__network-component-wrapper">
|
||||||
<NetworkIndicator
|
<NetworkIndicator
|
||||||
network={network}
|
network={network}
|
||||||
provider={provider}
|
provider={provider}
|
||||||
@ -135,5 +134,3 @@ class AppHeader extends Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AppHeader
|
|
||||||
|
@ -1,2 +1 @@
|
|||||||
import AppHeader from './app-header.container'
|
export { default } from './app-header.container'
|
||||||
module.exports = AppHeader
|
|
||||||
|
@ -30,21 +30,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__metafox {
|
&__metafox-logo {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
|
||||||
|
|
||||||
&__beta-label {
|
&--icon {
|
||||||
font-family: Roboto;
|
@media screen and (min-width: $break-large) {
|
||||||
text-transform: uppercase;
|
display: none;
|
||||||
font-weight: 500;
|
}
|
||||||
font-size: .8rem;
|
}
|
||||||
color: $buttercup;
|
|
||||||
margin-left: 5px;
|
|
||||||
line-height: initial;
|
|
||||||
|
|
||||||
@media screen and (max-width: 575px) {
|
&--horizontal {
|
||||||
display: none;
|
@media screen and (max-width: $break-small) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,31 +81,10 @@
|
|||||||
flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.app-header h1 {
|
&__network-component-wrapper {
|
||||||
font-family: Roboto;
|
display: flex;
|
||||||
text-transform: uppercase;
|
flex-direction: row;
|
||||||
font-weight: 400;
|
align-items: center;
|
||||||
font-size: 1.1rem;
|
|
||||||
position: relative;
|
|
||||||
padding-left: 15px;
|
|
||||||
color: #5b5d67;
|
|
||||||
|
|
||||||
@media screen and (max-width: 575px) {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h2.page-subtitle {
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: #aeaeae;
|
|
||||||
font-size: 1em;
|
|
||||||
margin: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-component-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
@ -27,6 +27,7 @@ function EnsInput () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EnsInput.prototype.onChange = function (recipient) {
|
EnsInput.prototype.onChange = function (recipient) {
|
||||||
|
|
||||||
const network = this.props.network
|
const network = this.props.network
|
||||||
const networkHasEnsSupport = getNetworkEnsSupport(network)
|
const networkHasEnsSupport = getNetworkEnsSupport(network)
|
||||||
|
|
||||||
@ -54,6 +55,7 @@ EnsInput.prototype.render = function () {
|
|||||||
const opts = extend(props, {
|
const opts = extend(props, {
|
||||||
list: 'addresses',
|
list: 'addresses',
|
||||||
onChange: this.onChange.bind(this),
|
onChange: this.onChange.bind(this),
|
||||||
|
qrScanner: true,
|
||||||
})
|
})
|
||||||
return h('div', {
|
return h('div', {
|
||||||
style: { width: '100%', position: 'relative' },
|
style: { width: '100%', position: 'relative' },
|
||||||
|
@ -19,3 +19,5 @@
|
|||||||
@import './sender-to-recipient/index';
|
@import './sender-to-recipient/index';
|
||||||
|
|
||||||
@import './tabs/index';
|
@import './tabs/index';
|
||||||
|
|
||||||
|
@import './app-header/index';
|
||||||
|
@ -60,7 +60,8 @@ AccountDetailsModal.prototype.render = function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
let exportPrivateKeyFeatureEnabled = true
|
let exportPrivateKeyFeatureEnabled = true
|
||||||
if (keyring.type === 'Trezor Hardware') {
|
// This feature is disabled for hardware wallets
|
||||||
|
if (keyring.type.search('Hardware') !== -1) {
|
||||||
exportPrivateKeyFeatureEnabled = false
|
exportPrivateKeyFeatureEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
@import './customize-gas/index';
|
@import './customize-gas/index';
|
||||||
|
|
||||||
|
@import './qr-scanner/index';
|
||||||
|
|
||||||
.modal-container {
|
.modal-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -21,6 +21,7 @@ const CustomizeGasModal = require('../customize-gas-modal')
|
|||||||
const NotifcationModal = require('./notification-modal')
|
const NotifcationModal = require('./notification-modal')
|
||||||
const ConfirmResetAccount = require('./confirm-reset-account')
|
const ConfirmResetAccount = require('./confirm-reset-account')
|
||||||
const ConfirmRemoveAccount = require('./confirm-remove-account')
|
const ConfirmRemoveAccount = require('./confirm-remove-account')
|
||||||
|
const QRScanner = require('./qr-scanner')
|
||||||
const TransactionConfirmed = require('./transaction-confirmed')
|
const TransactionConfirmed = require('./transaction-confirmed')
|
||||||
const WelcomeBeta = require('./welcome-beta')
|
const WelcomeBeta = require('./welcome-beta')
|
||||||
const Notification = require('./notification')
|
const Notification = require('./notification')
|
||||||
@ -346,6 +347,18 @@ const MODALS = {
|
|||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
QR_SCANNER: {
|
||||||
|
contents: h(QRScanner),
|
||||||
|
mobileModalStyle: {
|
||||||
|
...modalContainerMobileStyle,
|
||||||
|
},
|
||||||
|
laptopModalStyle: {
|
||||||
|
...modalContainerLaptopStyle,
|
||||||
|
},
|
||||||
|
contentStyle: {
|
||||||
|
borderRadius: '8px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
DEFAULT: {
|
DEFAULT: {
|
||||||
contents: [],
|
contents: [],
|
||||||
|
2
ui/app/components/modals/qr-scanner/index.js
Normal file
2
ui/app/components/modals/qr-scanner/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import QrScanner from './qr-scanner.container'
|
||||||
|
module.exports = QrScanner
|
83
ui/app/components/modals/qr-scanner/index.scss
Normal file
83
ui/app/components/modals/qr-scanner/index.scss
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
.qr-scanner {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 16px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
|
||||||
|
&__video-wrapper {
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
height: 275px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
video {
|
||||||
|
transform: scaleX(-1);
|
||||||
|
width: auto;
|
||||||
|
height: 275px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__status {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__image {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 16px 0 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__error {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
padding: 20px;
|
||||||
|
flex-direction: row;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:last-of-type {
|
||||||
|
margin-right: 0;
|
||||||
|
background-color: #009eec;
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close::after {
|
||||||
|
content: '\00D7';
|
||||||
|
font-size: 35px;
|
||||||
|
color: #9b9b9b;
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
216
ui/app/components/modals/qr-scanner/qr-scanner.component.js
Normal file
216
ui/app/components/modals/qr-scanner/qr-scanner.component.js
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { BrowserQRCodeReader } from '@zxing/library'
|
||||||
|
import adapter from 'webrtc-adapter' // eslint-disable-line import/no-nodejs-modules, no-unused-vars
|
||||||
|
import Spinner from '../../spinner'
|
||||||
|
import WebcamUtils from '../../../../lib/webcam-utils'
|
||||||
|
import PageContainerFooter from '../../page-container/page-container-footer/page-container-footer.component'
|
||||||
|
|
||||||
|
export default class QrScanner extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
hideModal: PropTypes.func.isRequired,
|
||||||
|
qrCodeDetected: PropTypes.func,
|
||||||
|
scanQrCode: PropTypes.func,
|
||||||
|
error: PropTypes.bool,
|
||||||
|
errorType: PropTypes.string,
|
||||||
|
}
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (props, context) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
ready: false,
|
||||||
|
msg: context.t('accessingYourCamera'),
|
||||||
|
}
|
||||||
|
this.codeReader = null
|
||||||
|
this.permissionChecker = null
|
||||||
|
this.needsToReinit = false
|
||||||
|
|
||||||
|
// Clear pre-existing qr code data before scanning
|
||||||
|
this.props.qrCodeDetected(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.initCamera()
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkPermisisions () {
|
||||||
|
const { permissions } = await WebcamUtils.checkStatus()
|
||||||
|
if (permissions) {
|
||||||
|
clearTimeout(this.permissionChecker)
|
||||||
|
// Let the video stream load first...
|
||||||
|
setTimeout(_ => {
|
||||||
|
this.setState({
|
||||||
|
ready: true,
|
||||||
|
msg: this.context.t('scanInstructions'),
|
||||||
|
})
|
||||||
|
if (this.needsToReinit) {
|
||||||
|
this.initCamera()
|
||||||
|
this.needsToReinit = false
|
||||||
|
}
|
||||||
|
}, 2000)
|
||||||
|
} else {
|
||||||
|
// Keep checking for permissions
|
||||||
|
this.permissionChecker = setTimeout(_ => {
|
||||||
|
this.checkPermisisions()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
clearTimeout(this.permissionChecker)
|
||||||
|
if (this.codeReader) {
|
||||||
|
this.codeReader.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initCamera () {
|
||||||
|
this.codeReader = new BrowserQRCodeReader()
|
||||||
|
this.codeReader.getVideoInputDevices()
|
||||||
|
.then(videoInputDevices => {
|
||||||
|
clearTimeout(this.permissionChecker)
|
||||||
|
this.checkPermisisions()
|
||||||
|
this.codeReader.decodeFromInputVideoDevice(undefined, 'video')
|
||||||
|
.then(content => {
|
||||||
|
const result = this.parseContent(content.text)
|
||||||
|
if (result.type !== 'unknown') {
|
||||||
|
this.props.qrCodeDetected(result)
|
||||||
|
this.stopAndClose()
|
||||||
|
} else {
|
||||||
|
this.setState({msg: this.context.t('unknownQrCode')})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
if (err && err.name === 'NotAllowedError') {
|
||||||
|
this.setState({msg: this.context.t('youNeedToAllowCameraAccess')})
|
||||||
|
clearTimeout(this.permissionChecker)
|
||||||
|
this.needsToReinit = true
|
||||||
|
this.checkPermisisions()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('[QR-SCANNER]: getVideoInputDevices threw an exception: ', err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
parseContent (content) {
|
||||||
|
let type = 'unknown'
|
||||||
|
let values = {}
|
||||||
|
|
||||||
|
// Here we could add more cases
|
||||||
|
// To parse other type of links
|
||||||
|
// For ex. EIP-681 (https://eips.ethereum.org/EIPS/eip-681)
|
||||||
|
|
||||||
|
|
||||||
|
// Ethereum address links - fox ex. ethereum:0x.....1111
|
||||||
|
if (content.split('ethereum:').length > 1) {
|
||||||
|
|
||||||
|
type = 'address'
|
||||||
|
values = {'address': content.split('ethereum:')[1] }
|
||||||
|
|
||||||
|
// Regular ethereum addresses - fox ex. 0x.....1111
|
||||||
|
} else if (content.substring(0, 2).toLowerCase() === '0x') {
|
||||||
|
|
||||||
|
type = 'address'
|
||||||
|
values = {'address': content }
|
||||||
|
|
||||||
|
}
|
||||||
|
return {type, values}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
stopAndClose = () => {
|
||||||
|
if (this.codeReader) {
|
||||||
|
this.codeReader.reset()
|
||||||
|
}
|
||||||
|
this.setState({ ready: false })
|
||||||
|
this.props.hideModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
tryAgain = () => {
|
||||||
|
// close the modal
|
||||||
|
this.stopAndClose()
|
||||||
|
// wait for the animation and try again
|
||||||
|
setTimeout(_ => {
|
||||||
|
this.props.scanQrCode()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderVideo () {
|
||||||
|
return (
|
||||||
|
<div className={'qr-scanner__content__video-wrapper'}>
|
||||||
|
<video
|
||||||
|
id="video"
|
||||||
|
style={{
|
||||||
|
display: this.state.ready ? 'block' : 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{ !this.state.ready ? <Spinner color={'#F7C06C'} /> : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderErrorModal () {
|
||||||
|
let title, msg
|
||||||
|
|
||||||
|
if (this.props.error) {
|
||||||
|
if (this.props.errorType === 'NO_WEBCAM_FOUND') {
|
||||||
|
title = this.context.t('noWebcamFoundTitle')
|
||||||
|
msg = this.context.t('noWebcamFound')
|
||||||
|
} else {
|
||||||
|
title = this.context.t('unknownCameraErrorTitle')
|
||||||
|
msg = this.context.t('unknownCameraError')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="qr-scanner">
|
||||||
|
<div className="qr-scanner__close" onClick={this.stopAndClose}></div>
|
||||||
|
|
||||||
|
<div className="qr-scanner__image">
|
||||||
|
<img src={'images/webcam.svg'} width={70} height={70} />
|
||||||
|
</div>
|
||||||
|
<div className="qr-scanner__title">
|
||||||
|
{ title }
|
||||||
|
</div>
|
||||||
|
<div className={'qr-scanner__error'}>
|
||||||
|
{msg}
|
||||||
|
</div>
|
||||||
|
<PageContainerFooter
|
||||||
|
onCancel={this.stopAndClose}
|
||||||
|
onSubmit={this.tryAgain}
|
||||||
|
cancelText={this.context.t('cancel')}
|
||||||
|
submitText={this.context.t('tryAgain')}
|
||||||
|
submitButtonType="confirm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { t } = this.context
|
||||||
|
|
||||||
|
if (this.props.error) {
|
||||||
|
return this.renderErrorModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="qr-scanner">
|
||||||
|
<div className="qr-scanner__close" onClick={this.stopAndClose}></div>
|
||||||
|
<div className="qr-scanner__title">
|
||||||
|
{ `${t('scanQrCode')}` }
|
||||||
|
</div>
|
||||||
|
<div className="qr-scanner__content">
|
||||||
|
{ this.renderVideo() }
|
||||||
|
</div>
|
||||||
|
<div className={'qr-scanner__status'}>
|
||||||
|
{this.state.msg}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
24
ui/app/components/modals/qr-scanner/qr-scanner.container.js
Normal file
24
ui/app/components/modals/qr-scanner/qr-scanner.container.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { connect } from 'react-redux'
|
||||||
|
import QrScanner from './qr-scanner.component'
|
||||||
|
|
||||||
|
const { hideModal, qrCodeDetected, showQrScanner } = require('../../../actions')
|
||||||
|
import {
|
||||||
|
SEND_ROUTE,
|
||||||
|
} from '../../../routes'
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
return {
|
||||||
|
error: state.appState.modal.modalState.props.error,
|
||||||
|
errorType: state.appState.modal.modalState.props.errorType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
hideModal: () => dispatch(hideModal()),
|
||||||
|
qrCodeDetected: (data) => dispatch(qrCodeDetected(data)),
|
||||||
|
scanQrCode: () => dispatch(showQrScanner(SEND_ROUTE)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(QrScanner)
|
@ -2,16 +2,75 @@ const { Component } = require('react')
|
|||||||
const PropTypes = require('prop-types')
|
const PropTypes = require('prop-types')
|
||||||
const h = require('react-hyperscript')
|
const h = require('react-hyperscript')
|
||||||
const genAccountLink = require('../../../../../lib/account-link.js')
|
const genAccountLink = require('../../../../../lib/account-link.js')
|
||||||
|
const Select = require('react-select').default
|
||||||
|
|
||||||
class AccountList extends Component {
|
class AccountList extends Component {
|
||||||
constructor (props, context) {
|
constructor (props, context) {
|
||||||
super(props)
|
super(props)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHdPaths () {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: `Ledger Live`,
|
||||||
|
value: `m/44'/60'/0'/0/0`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: `Legacy (MEW / MyCrypto)`,
|
||||||
|
value: `m/44'/60'/0'`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
goToNextPage = () => {
|
||||||
|
// If we have < 5 accounts, it's restricted by BIP-44
|
||||||
|
if (this.props.accounts.length === 5) {
|
||||||
|
this.props.getPage(this.props.device, 1, this.props.selectedPath)
|
||||||
|
} else {
|
||||||
|
this.props.onAccountRestriction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
goToPreviousPage = () => {
|
||||||
|
this.props.getPage(this.props.device, -1, this.props.selectedPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderHdPathSelector () {
|
||||||
|
const { onPathChange, selectedPath } = this.props
|
||||||
|
|
||||||
|
const options = this.getHdPaths()
|
||||||
|
return h('div', [
|
||||||
|
h('h3.hw-connect__hdPath__title', {}, this.context.t('selectHdPath')),
|
||||||
|
h('p.hw-connect__msg', {}, this.context.t('selectPathHelp')),
|
||||||
|
h('div.hw-connect__hdPath', [
|
||||||
|
h(Select, {
|
||||||
|
className: 'hw-connect__hdPath__select',
|
||||||
|
name: 'hd-path-select',
|
||||||
|
clearable: false,
|
||||||
|
value: selectedPath,
|
||||||
|
options,
|
||||||
|
onChange: (opt) => {
|
||||||
|
onPathChange(opt.value)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
capitalizeDevice (device) {
|
||||||
|
return device.slice(0, 1).toUpperCase() + device.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
renderHeader () {
|
renderHeader () {
|
||||||
|
const { device } = this.props
|
||||||
return (
|
return (
|
||||||
h('div.hw-connect', [
|
h('div.hw-connect', [
|
||||||
h('h3.hw-connect__title', {}, this.context.t('selectAnAccount')),
|
|
||||||
|
h('h3.hw-connect__unlock-title', {}, `${this.context.t('unlock')} ${this.capitalizeDevice(device)}`),
|
||||||
|
|
||||||
|
device.toLowerCase() === 'ledger' ? this.renderHdPathSelector() : null,
|
||||||
|
|
||||||
|
h('h3.hw-connect__hdPath__title', {}, this.context.t('selectAnAccount')),
|
||||||
h('p.hw-connect__msg', {}, this.context.t('selectAnAccountHelp')),
|
h('p.hw-connect__msg', {}, this.context.t('selectAnAccountHelp')),
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
@ -61,7 +120,7 @@ class AccountList extends Component {
|
|||||||
h(
|
h(
|
||||||
'button.hw-list-pagination__button',
|
'button.hw-list-pagination__button',
|
||||||
{
|
{
|
||||||
onClick: () => this.props.getPage(-1),
|
onClick: this.goToPreviousPage,
|
||||||
},
|
},
|
||||||
`< ${this.context.t('prev')}`
|
`< ${this.context.t('prev')}`
|
||||||
),
|
),
|
||||||
@ -69,7 +128,7 @@ class AccountList extends Component {
|
|||||||
h(
|
h(
|
||||||
'button.hw-list-pagination__button',
|
'button.hw-list-pagination__button',
|
||||||
{
|
{
|
||||||
onClick: () => this.props.getPage(1),
|
onClick: this.goToNextPage,
|
||||||
},
|
},
|
||||||
`${this.context.t('next')} >`
|
`${this.context.t('next')} >`
|
||||||
),
|
),
|
||||||
@ -95,7 +154,7 @@ class AccountList extends Component {
|
|||||||
h(
|
h(
|
||||||
`button.btn-primary.btn--large.new-account-connect-form__button.unlock ${disabled ? '.btn-primary--disabled' : ''}`,
|
`button.btn-primary.btn--large.new-account-connect-form__button.unlock ${disabled ? '.btn-primary--disabled' : ''}`,
|
||||||
{
|
{
|
||||||
onClick: this.props.onUnlockAccount.bind(this),
|
onClick: this.props.onUnlockAccount.bind(this, this.props.device),
|
||||||
...buttonProps,
|
...buttonProps,
|
||||||
},
|
},
|
||||||
[this.context.t('unlock')]
|
[this.context.t('unlock')]
|
||||||
@ -106,7 +165,7 @@ class AccountList extends Component {
|
|||||||
renderForgetDevice () {
|
renderForgetDevice () {
|
||||||
return h('div.hw-forget-device-container', {}, [
|
return h('div.hw-forget-device-container', {}, [
|
||||||
h('a', {
|
h('a', {
|
||||||
onClick: this.props.onForgetDevice.bind(this),
|
onClick: this.props.onForgetDevice.bind(this, this.props.device),
|
||||||
}, this.context.t('forgetDevice')),
|
}, this.context.t('forgetDevice')),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@ -125,6 +184,9 @@ class AccountList extends Component {
|
|||||||
|
|
||||||
|
|
||||||
AccountList.propTypes = {
|
AccountList.propTypes = {
|
||||||
|
onPathChange: PropTypes.func.isRequired,
|
||||||
|
selectedPath: PropTypes.string.isRequired,
|
||||||
|
device: PropTypes.string.isRequired,
|
||||||
accounts: PropTypes.array.isRequired,
|
accounts: PropTypes.array.isRequired,
|
||||||
onAccountChange: PropTypes.func.isRequired,
|
onAccountChange: PropTypes.func.isRequired,
|
||||||
onForgetDevice: PropTypes.func.isRequired,
|
onForgetDevice: PropTypes.func.isRequired,
|
||||||
@ -134,6 +196,7 @@ AccountList.propTypes = {
|
|||||||
history: PropTypes.object,
|
history: PropTypes.object,
|
||||||
onUnlockAccount: PropTypes.func,
|
onUnlockAccount: PropTypes.func,
|
||||||
onCancel: PropTypes.func,
|
onCancel: PropTypes.func,
|
||||||
|
onAccountRestriction: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountList.contextTypes = {
|
AccountList.contextTypes = {
|
||||||
|
@ -5,6 +5,52 @@ const h = require('react-hyperscript')
|
|||||||
class ConnectScreen extends Component {
|
class ConnectScreen extends Component {
|
||||||
constructor (props, context) {
|
constructor (props, context) {
|
||||||
super(props)
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
selectedDevice: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connect = () => {
|
||||||
|
if (this.state.selectedDevice) {
|
||||||
|
this.props.connectToHardwareWallet(this.state.selectedDevice)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
renderConnectToTrezorButton () {
|
||||||
|
return h(
|
||||||
|
`button.hw-connect__btn${this.state.selectedDevice === 'trezor' ? '.selected' : ''}`,
|
||||||
|
{ onClick: _ => this.setState({selectedDevice: 'trezor'}) },
|
||||||
|
h('img.hw-connect__btn__img', {
|
||||||
|
src: 'images/trezor-logo.svg',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderConnectToLedgerButton () {
|
||||||
|
return h(
|
||||||
|
`button.hw-connect__btn${this.state.selectedDevice === 'ledger' ? '.selected' : ''}`,
|
||||||
|
{ onClick: _ => this.setState({selectedDevice: 'ledger'}) },
|
||||||
|
h('img.hw-connect__btn__img', {
|
||||||
|
src: 'images/ledger-logo.svg',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderButtons () {
|
||||||
|
return (
|
||||||
|
h('div', {}, [
|
||||||
|
h('div.hw-connect__btn-wrapper', {}, [
|
||||||
|
this.renderConnectToLedgerButton(),
|
||||||
|
this.renderConnectToTrezorButton(),
|
||||||
|
]),
|
||||||
|
h(
|
||||||
|
`button.hw-connect__connect-btn${!this.state.selectedDevice ? '.disabled' : ''}`,
|
||||||
|
{ onClick: this.connect },
|
||||||
|
this.context.t('connect')
|
||||||
|
),
|
||||||
|
])
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderUnsupportedBrowser () {
|
renderUnsupportedBrowser () {
|
||||||
@ -12,7 +58,7 @@ class ConnectScreen extends Component {
|
|||||||
h('div.new-account-connect-form.unsupported-browser', {}, [
|
h('div.new-account-connect-form.unsupported-browser', {}, [
|
||||||
h('div.hw-connect', [
|
h('div.hw-connect', [
|
||||||
h('h3.hw-connect__title', {}, this.context.t('browserNotSupported')),
|
h('h3.hw-connect__title', {}, this.context.t('browserNotSupported')),
|
||||||
h('p.hw-connect__msg', {}, this.context.t('chromeRequiredForTrezor')),
|
h('p.hw-connect__msg', {}, this.context.t('chromeRequiredForHardwareWallets')),
|
||||||
]),
|
]),
|
||||||
h(
|
h(
|
||||||
'button.btn-primary.btn--large',
|
'button.btn-primary.btn--large',
|
||||||
@ -30,29 +76,31 @@ class ConnectScreen extends Component {
|
|||||||
renderHeader () {
|
renderHeader () {
|
||||||
return (
|
return (
|
||||||
h('div.hw-connect__header', {}, [
|
h('div.hw-connect__header', {}, [
|
||||||
h('h3.hw-connect__header__title', {}, this.context.t(`hardwareSupport`)),
|
h('h3.hw-connect__header__title', {}, this.context.t(`hardwareWallets`)),
|
||||||
h('p.hw-connect__header__msg', {}, this.context.t(`hardwareSupportMsg`)),
|
h('p.hw-connect__header__msg', {}, this.context.t(`hardwareWalletsMsg`)),
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAffiliateLinks () {
|
||||||
|
const links = {
|
||||||
|
trezor: `<a class='hw-connect__get-hw__link' href='https://shop.trezor.io/?a=metamask' target='_blank'>Trezor</a>`,
|
||||||
|
ledger: `<a class='hw-connect__get-hw__link' href='https://www.ledger.com/products/ledger-nano-s?r=17c4991a03fa&tracker=MY_TRACKER' target='_blank'>Ledger</a>`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = this.context.t('orderOneHere')
|
||||||
|
const response = text.replace('Trezor', links.trezor).replace('Ledger', links.ledger)
|
||||||
|
|
||||||
|
return h('div.hw-connect__get-hw__msg', { dangerouslySetInnerHTML: {__html: response }})
|
||||||
|
}
|
||||||
|
|
||||||
renderTrezorAffiliateLink () {
|
renderTrezorAffiliateLink () {
|
||||||
return h('div.hw-connect__get-trezor', {}, [
|
return h('div.hw-connect__get-hw', {}, [
|
||||||
h('p.hw-connect__get-trezor__msg', {}, this.context.t(`dontHaveATrezorWallet`)),
|
h('p.hw-connect__get-hw__msg', {}, this.context.t(`dontHaveAHardwareWallet`)),
|
||||||
h('a.hw-connect__get-trezor__link', {
|
this.getAffiliateLinks(),
|
||||||
href: 'https://shop.trezor.io/?a=metamask',
|
|
||||||
target: '_blank',
|
|
||||||
}, this.context.t('orderOneHere')),
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
renderConnectToTrezorButton () {
|
|
||||||
return h(
|
|
||||||
'button.btn-primary.btn--large',
|
|
||||||
{ onClick: this.props.connectToTrezor.bind(this) },
|
|
||||||
this.props.btnText
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollToTutorial = (e) => {
|
scrollToTutorial = (e) => {
|
||||||
if (this.referenceNode) this.referenceNode.scrollIntoView({behavior: 'smooth'})
|
if (this.referenceNode) this.referenceNode.scrollIntoView({behavior: 'smooth'})
|
||||||
@ -102,7 +150,7 @@ class ConnectScreen extends Component {
|
|||||||
return (
|
return (
|
||||||
h('div.hw-connect__footer', {}, [
|
h('div.hw-connect__footer', {}, [
|
||||||
h('h3.hw-connect__footer__title', {}, this.context.t(`readyToConnect`)),
|
h('h3.hw-connect__footer__title', {}, this.context.t(`readyToConnect`)),
|
||||||
this.renderConnectToTrezorButton(),
|
this.renderButtons(),
|
||||||
h('p.hw-connect__footer__msg', {}, [
|
h('p.hw-connect__footer__msg', {}, [
|
||||||
this.context.t(`havingTroubleConnecting`),
|
this.context.t(`havingTroubleConnecting`),
|
||||||
h('a.hw-connect__footer__link', {
|
h('a.hw-connect__footer__link', {
|
||||||
@ -118,8 +166,8 @@ class ConnectScreen extends Component {
|
|||||||
return (
|
return (
|
||||||
h('div.new-account-connect-form', {}, [
|
h('div.new-account-connect-form', {}, [
|
||||||
this.renderHeader(),
|
this.renderHeader(),
|
||||||
|
this.renderButtons(),
|
||||||
this.renderTrezorAffiliateLink(),
|
this.renderTrezorAffiliateLink(),
|
||||||
this.renderConnectToTrezorButton(),
|
|
||||||
this.renderLearnMore(),
|
this.renderLearnMore(),
|
||||||
this.renderTutorialSteps(),
|
this.renderTutorialSteps(),
|
||||||
this.renderFooter(),
|
this.renderFooter(),
|
||||||
@ -136,8 +184,7 @@ class ConnectScreen extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ConnectScreen.propTypes = {
|
ConnectScreen.propTypes = {
|
||||||
connectToTrezor: PropTypes.func.isRequired,
|
connectToHardwareWallet: PropTypes.func.isRequired,
|
||||||
btnText: PropTypes.string.isRequired,
|
|
||||||
browserSupported: PropTypes.bool.isRequired,
|
browserSupported: PropTypes.bool.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,17 +7,19 @@ const ConnectScreen = require('./connect-screen')
|
|||||||
const AccountList = require('./account-list')
|
const AccountList = require('./account-list')
|
||||||
const { DEFAULT_ROUTE } = require('../../../../routes')
|
const { DEFAULT_ROUTE } = require('../../../../routes')
|
||||||
const { formatBalance } = require('../../../../util')
|
const { formatBalance } = require('../../../../util')
|
||||||
|
const { getPlatform } = require('../../../../../../app/scripts/lib/util')
|
||||||
|
const { PLATFORM_FIREFOX } = require('../../../../../../app/scripts/lib/enums')
|
||||||
|
|
||||||
class ConnectHardwareForm extends Component {
|
class ConnectHardwareForm extends Component {
|
||||||
constructor (props, context) {
|
constructor (props, context) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
error: null,
|
error: null,
|
||||||
btnText: context.t('connectToTrezor'),
|
|
||||||
selectedAccount: null,
|
selectedAccount: null,
|
||||||
accounts: [],
|
accounts: [],
|
||||||
browserSupported: true,
|
browserSupported: true,
|
||||||
unlocked: false,
|
unlocked: false,
|
||||||
|
device: null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,25 +40,43 @@ class ConnectHardwareForm extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async checkIfUnlocked () {
|
async checkIfUnlocked () {
|
||||||
const unlocked = await this.props.checkHardwareStatus('trezor')
|
['trezor', 'ledger'].forEach(async device => {
|
||||||
if (unlocked) {
|
const unlocked = await this.props.checkHardwareStatus(device, this.props.defaultHdPaths[device])
|
||||||
this.setState({unlocked: true})
|
if (unlocked) {
|
||||||
this.getPage(0)
|
this.setState({unlocked: true})
|
||||||
}
|
this.getPage(device, 0, this.props.defaultHdPaths[device])
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
connectToTrezor = () => {
|
connectToHardwareWallet = (device) => {
|
||||||
|
// Ledger hardware wallets are not supported on firefox
|
||||||
|
if (getPlatform() === PLATFORM_FIREFOX && device === 'ledger') {
|
||||||
|
this.setState({ browserSupported: false, error: null})
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
if (this.state.accounts.length) {
|
if (this.state.accounts.length) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
this.setState({ btnText: this.context.t('connecting')})
|
|
||||||
this.getPage(0)
|
// Default values
|
||||||
|
this.getPage(device, 0, this.props.defaultHdPaths[device])
|
||||||
|
}
|
||||||
|
|
||||||
|
onPathChange = (path) => {
|
||||||
|
this.props.setHardwareWalletDefaultHdPath({device: this.state.device, path})
|
||||||
|
this.getPage(this.state.device, 0, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
onAccountChange = (account) => {
|
onAccountChange = (account) => {
|
||||||
this.setState({selectedAccount: account.toString(), error: null})
|
this.setState({selectedAccount: account.toString(), error: null})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onAccountRestriction = () => {
|
||||||
|
this.setState({error: this.context.t('ledgerAccountRestriction') })
|
||||||
|
}
|
||||||
|
|
||||||
showTemporaryAlert () {
|
showTemporaryAlert () {
|
||||||
this.props.showAlert(this.context.t('hardwareWalletConnected'))
|
this.props.showAlert(this.context.t('hardwareWalletConnected'))
|
||||||
// Autohide the alert after 5 seconds
|
// Autohide the alert after 5 seconds
|
||||||
@ -65,9 +85,9 @@ class ConnectHardwareForm extends Component {
|
|||||||
}, 5000)
|
}, 5000)
|
||||||
}
|
}
|
||||||
|
|
||||||
getPage = (page) => {
|
getPage = (device, page, hdPath) => {
|
||||||
this.props
|
this.props
|
||||||
.connectHardware('trezor', page)
|
.connectHardware(device, page, hdPath)
|
||||||
.then(accounts => {
|
.then(accounts => {
|
||||||
if (accounts.length) {
|
if (accounts.length) {
|
||||||
|
|
||||||
@ -77,7 +97,7 @@ class ConnectHardwareForm extends Component {
|
|||||||
this.showTemporaryAlert()
|
this.showTemporaryAlert()
|
||||||
}
|
}
|
||||||
|
|
||||||
const newState = { unlocked: true, error: null }
|
const newState = { unlocked: true, device, error: null }
|
||||||
// Default to the first account
|
// Default to the first account
|
||||||
if (this.state.selectedAccount === null) {
|
if (this.state.selectedAccount === null) {
|
||||||
accounts.forEach((a, i) => {
|
accounts.forEach((a, i) => {
|
||||||
@ -104,20 +124,18 @@ class ConnectHardwareForm extends Component {
|
|||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
if (e === 'Window blocked') {
|
if (e === 'Window blocked') {
|
||||||
this.setState({ browserSupported: false })
|
this.setState({ browserSupported: false, error: null})
|
||||||
} else {
|
} else if (e !== 'Window closed' && e !== 'Popup closed') {
|
||||||
this.setState({ error: e.toString() })
|
this.setState({ error: e.toString() })
|
||||||
}
|
}
|
||||||
this.setState({ btnText: this.context.t('connectToTrezor') })
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onForgetDevice = () => {
|
onForgetDevice = (device) => {
|
||||||
this.props.forgetDevice('trezor')
|
this.props.forgetDevice(device)
|
||||||
.then(_ => {
|
.then(_ => {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: null,
|
error: null,
|
||||||
btnText: this.context.t('connectToTrezor'),
|
|
||||||
selectedAccount: null,
|
selectedAccount: null,
|
||||||
accounts: [],
|
accounts: [],
|
||||||
unlocked: false,
|
unlocked: false,
|
||||||
@ -127,13 +145,13 @@ class ConnectHardwareForm extends Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onUnlockAccount = () => {
|
onUnlockAccount = (device) => {
|
||||||
|
|
||||||
if (this.state.selectedAccount === null) {
|
if (this.state.selectedAccount === null) {
|
||||||
this.setState({ error: this.context.t('accountSelectionRequired') })
|
this.setState({ error: this.context.t('accountSelectionRequired') })
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.unlockTrezorAccount(this.state.selectedAccount)
|
this.props.unlockHardwareWalletAccount(this.state.selectedAccount, device)
|
||||||
.then(_ => {
|
.then(_ => {
|
||||||
this.props.history.push(DEFAULT_ROUTE)
|
this.props.history.push(DEFAULT_ROUTE)
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
@ -154,13 +172,15 @@ class ConnectHardwareForm extends Component {
|
|||||||
renderContent () {
|
renderContent () {
|
||||||
if (!this.state.accounts.length) {
|
if (!this.state.accounts.length) {
|
||||||
return h(ConnectScreen, {
|
return h(ConnectScreen, {
|
||||||
connectToTrezor: this.connectToTrezor,
|
connectToHardwareWallet: this.connectToHardwareWallet,
|
||||||
btnText: this.state.btnText,
|
|
||||||
browserSupported: this.state.browserSupported,
|
browserSupported: this.state.browserSupported,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return h(AccountList, {
|
return h(AccountList, {
|
||||||
|
onPathChange: this.onPathChange,
|
||||||
|
selectedPath: this.props.defaultHdPaths[this.state.device],
|
||||||
|
device: this.state.device,
|
||||||
accounts: this.state.accounts,
|
accounts: this.state.accounts,
|
||||||
selectedAccount: this.state.selectedAccount,
|
selectedAccount: this.state.selectedAccount,
|
||||||
onAccountChange: this.onAccountChange,
|
onAccountChange: this.onAccountChange,
|
||||||
@ -170,6 +190,7 @@ class ConnectHardwareForm extends Component {
|
|||||||
onUnlockAccount: this.onUnlockAccount,
|
onUnlockAccount: this.onUnlockAccount,
|
||||||
onForgetDevice: this.onForgetDevice,
|
onForgetDevice: this.onForgetDevice,
|
||||||
onCancel: this.onCancel,
|
onCancel: this.onCancel,
|
||||||
|
onAccountRestriction: this.onAccountRestriction,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,13 +211,15 @@ ConnectHardwareForm.propTypes = {
|
|||||||
forgetDevice: PropTypes.func,
|
forgetDevice: PropTypes.func,
|
||||||
showAlert: PropTypes.func,
|
showAlert: PropTypes.func,
|
||||||
hideAlert: PropTypes.func,
|
hideAlert: PropTypes.func,
|
||||||
unlockTrezorAccount: PropTypes.func,
|
unlockHardwareWalletAccount: PropTypes.func,
|
||||||
|
setHardwareWalletDefaultHdPath: PropTypes.func,
|
||||||
numberOfExistingAccounts: PropTypes.number,
|
numberOfExistingAccounts: PropTypes.number,
|
||||||
history: PropTypes.object,
|
history: PropTypes.object,
|
||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
network: PropTypes.string,
|
network: PropTypes.string,
|
||||||
accounts: PropTypes.object,
|
accounts: PropTypes.object,
|
||||||
address: PropTypes.string,
|
address: PropTypes.string,
|
||||||
|
defaultHdPaths: PropTypes.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
@ -204,28 +227,35 @@ const mapStateToProps = state => {
|
|||||||
metamask: { network, selectedAddress, identities = {}, accounts = [] },
|
metamask: { network, selectedAddress, identities = {}, accounts = [] },
|
||||||
} = state
|
} = state
|
||||||
const numberOfExistingAccounts = Object.keys(identities).length
|
const numberOfExistingAccounts = Object.keys(identities).length
|
||||||
|
const {
|
||||||
|
appState: { defaultHdPaths },
|
||||||
|
} = state
|
||||||
|
|
||||||
return {
|
return {
|
||||||
network,
|
network,
|
||||||
accounts,
|
accounts,
|
||||||
address: selectedAddress,
|
address: selectedAddress,
|
||||||
numberOfExistingAccounts,
|
numberOfExistingAccounts,
|
||||||
|
defaultHdPaths,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
connectHardware: (deviceName, page) => {
|
setHardwareWalletDefaultHdPath: ({device, path}) => {
|
||||||
return dispatch(actions.connectHardware(deviceName, page))
|
return dispatch(actions.setHardwareWalletDefaultHdPath({device, path}))
|
||||||
},
|
},
|
||||||
checkHardwareStatus: (deviceName) => {
|
connectHardware: (deviceName, page, hdPath) => {
|
||||||
return dispatch(actions.checkHardwareStatus(deviceName))
|
return dispatch(actions.connectHardware(deviceName, page, hdPath))
|
||||||
|
},
|
||||||
|
checkHardwareStatus: (deviceName, hdPath) => {
|
||||||
|
return dispatch(actions.checkHardwareStatus(deviceName, hdPath))
|
||||||
},
|
},
|
||||||
forgetDevice: (deviceName) => {
|
forgetDevice: (deviceName) => {
|
||||||
return dispatch(actions.forgetDevice(deviceName))
|
return dispatch(actions.forgetDevice(deviceName))
|
||||||
},
|
},
|
||||||
unlockTrezorAccount: index => {
|
unlockHardwareWalletAccount: (index, deviceName, hdPath) => {
|
||||||
return dispatch(actions.unlockTrezorAccount(index))
|
return dispatch(actions.unlockHardwareWalletAccount(index, deviceName, hdPath))
|
||||||
},
|
},
|
||||||
showImportPage: () => dispatch(actions.showImportPage()),
|
showImportPage: () => dispatch(actions.showImportPage()),
|
||||||
showConnectPage: () => dispatch(actions.showConnectPage()),
|
showConnectPage: () => dispatch(actions.showConnectPage()),
|
||||||
|
@ -11,6 +11,7 @@ export default class SendContent extends Component {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
updateGas: PropTypes.func,
|
updateGas: PropTypes.func,
|
||||||
|
scanQrCode: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@ -18,7 +19,10 @@ export default class SendContent extends Component {
|
|||||||
<PageContainerContent>
|
<PageContainerContent>
|
||||||
<div className="send-v2__form">
|
<div className="send-v2__form">
|
||||||
<SendFromRow />
|
<SendFromRow />
|
||||||
<SendToRow updateGas={(updateData) => this.props.updateGas(updateData)} />
|
<SendToRow
|
||||||
|
updateGas={(updateData) => this.props.updateGas(updateData)}
|
||||||
|
scanQrCode={ _ => this.props.scanQrCode()}
|
||||||
|
/>
|
||||||
<SendAmountRow updateGas={(updateData) => this.props.updateGas(updateData)} />
|
<SendAmountRow updateGas={(updateData) => this.props.updateGas(updateData)} />
|
||||||
<SendGasRow />
|
<SendGasRow />
|
||||||
<SendHexDataRow />
|
<SendHexDataRow />
|
||||||
|
@ -17,6 +17,7 @@ export default class SendToRow extends Component {
|
|||||||
updateGas: PropTypes.func,
|
updateGas: PropTypes.func,
|
||||||
updateSendTo: PropTypes.func,
|
updateSendTo: PropTypes.func,
|
||||||
updateSendToError: PropTypes.func,
|
updateSendToError: PropTypes.func,
|
||||||
|
scanQrCode: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -51,6 +52,7 @@ export default class SendToRow extends Component {
|
|||||||
showError={inError}
|
showError={inError}
|
||||||
>
|
>
|
||||||
<EnsInput
|
<EnsInput
|
||||||
|
scanQrCode={_ => this.props.scanQrCode()}
|
||||||
accounts={toAccounts}
|
accounts={toAccounts}
|
||||||
closeDropdown={() => closeToDropdown()}
|
closeDropdown={() => closeToDropdown()}
|
||||||
dropdownOpen={toDropdownOpen}
|
dropdownOpen={toDropdownOpen}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user