Merge branch 'develop' into v4.11.0
This commit is contained in:
commit
0c97fc1d9e
2
.babelrc
2
.babelrc
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"presets": [["env", { "debug": true }], "react", "stage-0"],
|
"presets": [["env", { "targets": { "browsers": [">0.25%", "not ie 11", "not op_mini all"] } } ], "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
|
||||||
|
|
|
@ -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
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
FIREFOX_VERSION='62.0'
|
||||||
|
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."
|
|
|
@ -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");
|
|
@ -9,6 +9,7 @@ package
|
||||||
# IDEs
|
# IDEs
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
.sublime-project
|
||||||
|
|
||||||
# VIM
|
# VIM
|
||||||
*.swp
|
*.swp
|
||||||
|
@ -20,6 +21,7 @@ temp
|
||||||
.DS_Store
|
.DS_Store
|
||||||
app/.DS_Store
|
app/.DS_Store
|
||||||
|
|
||||||
|
coverage/
|
||||||
dist
|
dist
|
||||||
builds/
|
builds/
|
||||||
disc/
|
disc/
|
||||||
|
@ -34,6 +36,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/
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"generator-mocha": {}
|
|
||||||
}
|
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -2,11 +2,25 @@
|
||||||
|
|
||||||
## Current Develop Branch
|
## Current Develop Branch
|
||||||
|
|
||||||
|
## 4.10.0 Mon Sep 17 2018
|
||||||
|
|
||||||
|
- [#4803](https://github.com/MetaMask/metamask-extension/pull/4803): Implement EIP-712: Sign typed data, but continue to support v1.
|
||||||
|
- [#4898](https://github.com/MetaMask/metamask-extension/pull/4898): Restore multiple consecutive accounts with balances.
|
||||||
|
- [#4279](https://github.com/MetaMask/metamask-extension/pull/4279): New BlockTracker and Json-Rpc-Engine based Provider.
|
||||||
|
- [#5050](https://github.com/MetaMask/metamask-extension/pull/5050): Add Ledger hardware wallet support.
|
||||||
|
- [#4919](https://github.com/MetaMask/metamask-extension/pull/4919): Refactor and Redesign Transaction List.
|
||||||
|
- [#5182](https://github.com/MetaMask/metamask-extension/pull/5182): Add Transaction Details to the Transaction List view.
|
||||||
|
- [#5229](https://github.com/MetaMask/metamask-extension/pull/5229): Clear old seed words when importing new seed words.
|
||||||
|
- [#5264](https://github.com/MetaMask/metamask-extension/pull/5264): Improve click area for adjustment arrows buttons.
|
||||||
|
- [#4606](https://github.com/MetaMask/metamask-extension/pull/4606): Add new metamask_watchAsset method.
|
||||||
|
- [#5189](https://github.com/MetaMask/metamask-extension/pull/5189): Fix bug where Ropsten loading message is shown when connecting to Kovan.
|
||||||
|
- [#5256](https://github.com/MetaMask/metamask-extension/pull/5256): Add mock EIP-1102 support
|
||||||
|
|
||||||
## 4.9.3 Wed Aug 15 2018
|
## 4.9.3 Wed Aug 15 2018
|
||||||
|
|
||||||
- (#4897)[https://github.com/MetaMask/metamask-extension/pull/4897]: QR code scan for recipient addresses.
|
- [#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.
|
- [#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.
|
- [#5060](https://github.com/MetaMask/metamask-extension/pull/5060): Fix bug where gas was not updating properly.
|
||||||
|
|
||||||
## 4.9.2 Mon Aug 09 2018
|
## 4.9.2 Mon Aug 09 2018
|
||||||
|
|
||||||
|
@ -33,6 +47,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
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,12 @@ If you're a web dapp developer, we've got two types of guides for you:
|
||||||
|
|
||||||
Uncompressed builds can be found in `/dist`, compressed builds can be found in `/builds` once they're built.
|
Uncompressed builds can be found in `/dist`, compressed builds can be found in `/builds` once they're built.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
You can read [our internal docs here](https://metamask.github.io/metamask-extension/).
|
||||||
|
|
||||||
|
You can re-generate the docs locally by running `npm run doc`, and contributors can update the hosted docs by running `npm run publish-docs`.
|
||||||
|
|
||||||
### Running Tests
|
### Running Tests
|
||||||
|
|
||||||
Requires `mocha` installed. Run `npm install -g mocha`.
|
Requires `mocha` installed. Run `npm install -g mocha`.
|
||||||
|
|
|
@ -796,7 +796,7 @@
|
||||||
"message": "Testovací faucet"
|
"message": "Testovací faucet"
|
||||||
},
|
},
|
||||||
"to": {
|
"to": {
|
||||||
"message": "Komu: "
|
"message": "Komu"
|
||||||
},
|
},
|
||||||
"toETHviaShapeShift": {
|
"toETHviaShapeShift": {
|
||||||
"message": "$1 na ETH přes ShapeShift",
|
"message": "$1 na ETH přes ShapeShift",
|
||||||
|
|
|
@ -775,7 +775,7 @@
|
||||||
"message": "Testfaucet"
|
"message": "Testfaucet"
|
||||||
},
|
},
|
||||||
"to": {
|
"to": {
|
||||||
"message": "An:"
|
"message": "An"
|
||||||
},
|
},
|
||||||
"toETHviaShapeShift": {
|
"toETHviaShapeShift": {
|
||||||
"message": "$1 an ETH via ShapeShift",
|
"message": "$1 an ETH via ShapeShift",
|
||||||
|
|
|
@ -17,6 +17,9 @@
|
||||||
"accountSelectionRequired": {
|
"accountSelectionRequired": {
|
||||||
"message": "You need to select an account!"
|
"message": "You need to select an account!"
|
||||||
},
|
},
|
||||||
|
"activityLog": {
|
||||||
|
"message": "activity log"
|
||||||
|
},
|
||||||
"address": {
|
"address": {
|
||||||
"message": "Address"
|
"message": "Address"
|
||||||
},
|
},
|
||||||
|
@ -29,6 +32,9 @@
|
||||||
"addTokens": {
|
"addTokens": {
|
||||||
"message": "Add Tokens"
|
"message": "Add Tokens"
|
||||||
},
|
},
|
||||||
|
"addSuggestedTokens": {
|
||||||
|
"message": "Add Suggested Tokens"
|
||||||
|
},
|
||||||
"addAcquiredTokens": {
|
"addAcquiredTokens": {
|
||||||
"message": "Add the tokens you've acquired using MetaMask"
|
"message": "Add the tokens you've acquired using MetaMask"
|
||||||
},
|
},
|
||||||
|
@ -119,8 +125,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"
|
||||||
|
@ -146,15 +152,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"
|
||||||
},
|
},
|
||||||
|
@ -213,6 +216,9 @@
|
||||||
"currentConversion": {
|
"currentConversion": {
|
||||||
"message": "Current Conversion"
|
"message": "Current Conversion"
|
||||||
},
|
},
|
||||||
|
"currentLanguage": {
|
||||||
|
"message": "Current Language"
|
||||||
|
},
|
||||||
"currentNetwork": {
|
"currentNetwork": {
|
||||||
"message": "Current Network"
|
"message": "Current Network"
|
||||||
},
|
},
|
||||||
|
@ -289,8 +295,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"
|
||||||
|
@ -426,11 +432,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?"
|
||||||
|
@ -454,6 +460,9 @@
|
||||||
"hideTokenPrompt": {
|
"hideTokenPrompt": {
|
||||||
"message": "Hide Token?"
|
"message": "Hide Token?"
|
||||||
},
|
},
|
||||||
|
"history": {
|
||||||
|
"message": "History"
|
||||||
|
},
|
||||||
"howToDeposit": {
|
"howToDeposit": {
|
||||||
"message": "How would you like to deposit Ether?"
|
"message": "How would you like to deposit Ether?"
|
||||||
},
|
},
|
||||||
|
@ -538,6 +547,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"
|
||||||
|
@ -587,6 +599,9 @@
|
||||||
"metamaskSeedWords": {
|
"metamaskSeedWords": {
|
||||||
"message": "MetaMask Seed Words"
|
"message": "MetaMask Seed Words"
|
||||||
},
|
},
|
||||||
|
"metamaskVersion": {
|
||||||
|
"message": "MetaMask Version"
|
||||||
|
},
|
||||||
"min": {
|
"min": {
|
||||||
"message": "Minimum"
|
"message": "Minimum"
|
||||||
},
|
},
|
||||||
|
@ -651,7 +666,7 @@
|
||||||
"message": "No transaction history."
|
"message": "No transaction history."
|
||||||
},
|
},
|
||||||
"noTransactions": {
|
"noTransactions": {
|
||||||
"message": "No Transactions"
|
"message": "You have no transactions"
|
||||||
},
|
},
|
||||||
"notFound": {
|
"notFound": {
|
||||||
"message": "Not Found"
|
"message": "Not Found"
|
||||||
|
@ -702,6 +717,9 @@
|
||||||
"pasteSeed": {
|
"pasteSeed": {
|
||||||
"message": "Paste your seed phrase here!"
|
"message": "Paste your seed phrase here!"
|
||||||
},
|
},
|
||||||
|
"pending": {
|
||||||
|
"message": "pending"
|
||||||
|
},
|
||||||
"personalAddressDetected": {
|
"personalAddressDetected": {
|
||||||
"message": "Personal address detected. Input the token contract address."
|
"message": "Personal address detected. Input the token contract address."
|
||||||
},
|
},
|
||||||
|
@ -730,6 +748,9 @@
|
||||||
"qrCode": {
|
"qrCode": {
|
||||||
"message": "Show QR Code"
|
"message": "Show QR Code"
|
||||||
},
|
},
|
||||||
|
"queue": {
|
||||||
|
"message": "Queue"
|
||||||
|
},
|
||||||
"readdToken": {
|
"readdToken": {
|
||||||
"message": "You can add this token back in the future by going go to “Add token” in your accounts options menu."
|
"message": "You can add this token back in the future by going go to “Add token” in your accounts options menu."
|
||||||
},
|
},
|
||||||
|
@ -845,6 +866,9 @@
|
||||||
"save": {
|
"save": {
|
||||||
"message": "Save"
|
"message": "Save"
|
||||||
},
|
},
|
||||||
|
"speedUp": {
|
||||||
|
"message": "speed up"
|
||||||
|
},
|
||||||
"speedUpTitle": {
|
"speedUpTitle": {
|
||||||
"message": "Speed Up Transaction"
|
"message": "Speed Up Transaction"
|
||||||
},
|
},
|
||||||
|
@ -870,6 +894,12 @@
|
||||||
"secretPhrase": {
|
"secretPhrase": {
|
||||||
"message": "Enter your secret twelve word phrase here to restore your vault."
|
"message": "Enter your secret twelve word phrase here to restore your vault."
|
||||||
},
|
},
|
||||||
|
"showHexData": {
|
||||||
|
"message": "Show Hex Data"
|
||||||
|
},
|
||||||
|
"showHexDataDescription": {
|
||||||
|
"message": "Select this to show the hex data field on the send screen"
|
||||||
|
},
|
||||||
"newPassword8Chars": {
|
"newPassword8Chars": {
|
||||||
"message": "New Password (min 8 chars)"
|
"message": "New Password (min 8 chars)"
|
||||||
},
|
},
|
||||||
|
@ -882,6 +912,9 @@
|
||||||
"selectCurrency": {
|
"selectCurrency": {
|
||||||
"message": "Select Currency"
|
"message": "Select Currency"
|
||||||
},
|
},
|
||||||
|
"selectLocale": {
|
||||||
|
"message": "Select Locale"
|
||||||
|
},
|
||||||
"selectService": {
|
"selectService": {
|
||||||
"message": "Select Service"
|
"message": "Select Service"
|
||||||
},
|
},
|
||||||
|
@ -897,6 +930,12 @@
|
||||||
"sendTokens": {
|
"sendTokens": {
|
||||||
"message": "Send Tokens"
|
"message": "Send Tokens"
|
||||||
},
|
},
|
||||||
|
"sentEther": {
|
||||||
|
"message": "sent ether"
|
||||||
|
},
|
||||||
|
"sentTokens": {
|
||||||
|
"message": "sent tokens"
|
||||||
|
},
|
||||||
"separateEachWord": {
|
"separateEachWord": {
|
||||||
"message": "Separate each word with a single space"
|
"message": "Separate each word with a single space"
|
||||||
},
|
},
|
||||||
|
@ -908,7 +947,10 @@
|
||||||
"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"
|
||||||
|
},
|
||||||
|
"outgoing": {
|
||||||
|
"message": "Outgoing"
|
||||||
},
|
},
|
||||||
"searchTokens": {
|
"searchTokens": {
|
||||||
"message": "Search Tokens"
|
"message": "Search Tokens"
|
||||||
|
@ -920,7 +962,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"
|
||||||
|
@ -967,6 +1015,9 @@
|
||||||
"sign": {
|
"sign": {
|
||||||
"message": "Sign"
|
"message": "Sign"
|
||||||
},
|
},
|
||||||
|
"signatureRequest": {
|
||||||
|
"message": "Signature Request"
|
||||||
|
},
|
||||||
"signed": {
|
"signed": {
|
||||||
"message": "Signed"
|
"message": "Signed"
|
||||||
},
|
},
|
||||||
|
@ -1019,7 +1070,7 @@
|
||||||
"message": "Test Faucet"
|
"message": "Test Faucet"
|
||||||
},
|
},
|
||||||
"to": {
|
"to": {
|
||||||
"message": "To: "
|
"message": "To"
|
||||||
},
|
},
|
||||||
"toETHviaShapeShift": {
|
"toETHviaShapeShift": {
|
||||||
"message": "$1 to ETH via ShapeShift",
|
"message": "$1 to ETH via ShapeShift",
|
||||||
|
@ -1049,6 +1100,27 @@
|
||||||
"total": {
|
"total": {
|
||||||
"message": "Total"
|
"message": "Total"
|
||||||
},
|
},
|
||||||
|
"transaction": {
|
||||||
|
"message": "transaction"
|
||||||
|
},
|
||||||
|
"transactionConfirmed": {
|
||||||
|
"message": "Transaction confirmed on $2."
|
||||||
|
},
|
||||||
|
"transactionCreated": {
|
||||||
|
"message": "Transaction created with a value of $1 on $2."
|
||||||
|
},
|
||||||
|
"transactionDropped": {
|
||||||
|
"message": "Transaction dropped on $2."
|
||||||
|
},
|
||||||
|
"transactionSubmitted": {
|
||||||
|
"message": "Transaction submitted on $2."
|
||||||
|
},
|
||||||
|
"transactionUpdated": {
|
||||||
|
"message": "Transaction updated on $2."
|
||||||
|
},
|
||||||
|
"transactionUpdatedGas": {
|
||||||
|
"message": "Transaction updated with a gas price of $1 on $2."
|
||||||
|
},
|
||||||
"transactions": {
|
"transactions": {
|
||||||
"message": "transactions"
|
"message": "transactions"
|
||||||
},
|
},
|
||||||
|
@ -1095,6 +1167,9 @@
|
||||||
"unavailable": {
|
"unavailable": {
|
||||||
"message": "Unavailable"
|
"message": "Unavailable"
|
||||||
},
|
},
|
||||||
|
"units": {
|
||||||
|
"message": "units"
|
||||||
|
},
|
||||||
"unknown": {
|
"unknown": {
|
||||||
"message": "Unknown"
|
"message": "Unknown"
|
||||||
},
|
},
|
||||||
|
@ -1122,6 +1197,9 @@
|
||||||
"unlockMessage": {
|
"unlockMessage": {
|
||||||
"message": "The decentralized web awaits"
|
"message": "The decentralized web awaits"
|
||||||
},
|
},
|
||||||
|
"updatedWithDate": {
|
||||||
|
"message": "Updated $1"
|
||||||
|
},
|
||||||
"uriErrorMsg": {
|
"uriErrorMsg": {
|
||||||
"message": "URIs require the appropriate HTTP/HTTPS prefix."
|
"message": "URIs require the appropriate HTTP/HTTPS prefix."
|
||||||
},
|
},
|
||||||
|
|
|
@ -772,7 +772,7 @@
|
||||||
"message": "Probar Faucet"
|
"message": "Probar Faucet"
|
||||||
},
|
},
|
||||||
"to": {
|
"to": {
|
||||||
"message": "Para:"
|
"message": "Para"
|
||||||
},
|
},
|
||||||
"toETHviaShapeShift": {
|
"toETHviaShapeShift": {
|
||||||
"message": "$1 a ETH via ShapeShift",
|
"message": "$1 a ETH via ShapeShift",
|
||||||
|
|
|
@ -490,7 +490,7 @@
|
||||||
"message": "Sélectionner un service"
|
"message": "Sélectionner un service"
|
||||||
},
|
},
|
||||||
"send": {
|
"send": {
|
||||||
"message": "Envoyé"
|
"message": "Envoyer"
|
||||||
},
|
},
|
||||||
"sendTokens": {
|
"sendTokens": {
|
||||||
"message": "Envoyer des jetons"
|
"message": "Envoyer des jetons"
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -784,7 +784,7 @@
|
||||||
"message": "Тестовый кран"
|
"message": "Тестовый кран"
|
||||||
},
|
},
|
||||||
"to": {
|
"to": {
|
||||||
"message": "Получатель: "
|
"message": "Получатель"
|
||||||
},
|
},
|
||||||
"toETHviaShapeShift": {
|
"toETHviaShapeShift": {
|
||||||
"message": "$1 в ETH через ShapeShift",
|
"message": "$1 в ETH через ShapeShift",
|
||||||
|
|
|
@ -796,7 +796,7 @@
|
||||||
"message": "சோதனை குழாய்"
|
"message": "சோதனை குழாய்"
|
||||||
},
|
},
|
||||||
"to": {
|
"to": {
|
||||||
"message": "பெறுநர்: "
|
"message": "பெறுநர்"
|
||||||
},
|
},
|
||||||
"toETHviaShapeShift": {
|
"toETHviaShapeShift": {
|
||||||
"message": "$ 1 முதல் ETH வரை வடிவம்",
|
"message": "$ 1 முதல் ETH வரை வடிவம்",
|
||||||
|
|
|
@ -796,7 +796,7 @@
|
||||||
"message": "Test Musluğu"
|
"message": "Test Musluğu"
|
||||||
},
|
},
|
||||||
"to": {
|
"to": {
|
||||||
"message": "Kime: "
|
"message": "Kime"
|
||||||
},
|
},
|
||||||
"toETHviaShapeShift": {
|
"toETHviaShapeShift": {
|
||||||
"message": "ShapeShift üstünden $1'dan ETH'e",
|
"message": "ShapeShift üstünden $1'dan ETH'e",
|
||||||
|
|
|
@ -23,6 +23,9 @@
|
||||||
"addTokens": {
|
"addTokens": {
|
||||||
"message": "添加代币"
|
"message": "添加代币"
|
||||||
},
|
},
|
||||||
|
"addAcquiredTokens": {
|
||||||
|
"message": "在Metamask上添加已用的代币"
|
||||||
|
},
|
||||||
"amount": {
|
"amount": {
|
||||||
"message": "数量"
|
"message": "数量"
|
||||||
},
|
},
|
||||||
|
@ -116,6 +119,9 @@
|
||||||
"confirmTransaction": {
|
"confirmTransaction": {
|
||||||
"message": "确认交易"
|
"message": "确认交易"
|
||||||
},
|
},
|
||||||
|
"connectHardwareWallet": {
|
||||||
|
"message": "链接硬件钱包"
|
||||||
|
},
|
||||||
"continue": {
|
"continue": {
|
||||||
"message": "继续"
|
"message": "继续"
|
||||||
},
|
},
|
||||||
|
@ -714,6 +720,9 @@
|
||||||
"search": {
|
"search": {
|
||||||
"message": "搜索"
|
"message": "搜索"
|
||||||
},
|
},
|
||||||
|
"searchResults": {
|
||||||
|
"message": "搜索结果"
|
||||||
|
},
|
||||||
"secretPhrase": {
|
"secretPhrase": {
|
||||||
"message": "输入12位助记词以恢复金库."
|
"message": "输入12位助记词以恢复金库."
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8.67589 0.641872C8.65169 0.642635 8.62756 0.644749 8.6036 0.648202H4.79279C4.55863 0.644896 4.34082 0.767704 4.22278 0.969601C4.10473 1.1715 4.10473 1.4212 4.22278 1.6231C4.34082 1.825 4.55863 1.9478 4.79279 1.9445H7.12113L0.437932 8.61587C0.268309 8.77843 0.19998 9.01984 0.259298 9.24697C0.318616 9.47411 0.496311 9.65149 0.723852 9.71071C0.951393 9.76992 1.19322 9.70171 1.35608 9.53239L8.03927 2.86102V5.18524C8.03596 5.41898 8.15899 5.6364 8.36124 5.75424C8.56349 5.87208 8.81364 5.87208 9.0159 5.75424C9.21815 5.6364 9.34118 5.41898 9.33787 5.18524V1.37863C9.36404 1.18976 9.30558 0.998955 9.17804 0.857009C9.0505 0.715062 8.86682 0.636369 8.67589 0.641872Z" fill="#359BDD"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 795 B |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -63,7 +63,6 @@
|
||||||
"activeTab",
|
"activeTab",
|
||||||
"webRequest",
|
"webRequest",
|
||||||
"*://*.eth/",
|
"*://*.eth/",
|
||||||
"*://*.test/",
|
|
||||||
"notifications"
|
"notifications"
|
||||||
],
|
],
|
||||||
"web_accessible_resources": [
|
"web_accessible_resources": [
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
ga('create', 'UA-68598031-1', 'auto' {'allowLinker':true});
|
ga('create', 'UA-68598031-1', 'auto' {'allowLinker':true});
|
||||||
ga('send', 'pageview');
|
ga('send', 'pageview');
|
||||||
ga('require', 'linker');
|
ga('require', 'linker');
|
||||||
ga('linker:autoLink', ['harrydenley.com', 'metamask.io'], false, true);
|
ga('linker:autoLink', ['metamask.io'], false, true);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -15,11 +15,11 @@ const asStream = require('obs-store/lib/asStream')
|
||||||
const ExtensionPlatform = require('./platforms/extension')
|
const ExtensionPlatform = require('./platforms/extension')
|
||||||
const Migrator = require('./lib/migrator/')
|
const Migrator = require('./lib/migrator/')
|
||||||
const migrations = require('./migrations/')
|
const migrations = require('./migrations/')
|
||||||
const PortStream = require('./lib/port-stream.js')
|
const PortStream = require('extension-port-stream')
|
||||||
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
|
||||||
|
|
||||||
|
@ -253,6 +256,7 @@ function setupController (initState, initLangCode) {
|
||||||
showUnconfirmedMessage: triggerUi,
|
showUnconfirmedMessage: triggerUi,
|
||||||
unlockAccountMessage: triggerUi,
|
unlockAccountMessage: triggerUi,
|
||||||
showUnapprovedTx: triggerUi,
|
showUnapprovedTx: triggerUi,
|
||||||
|
showWatchAssetUi: showWatchAssetUi,
|
||||||
// initial state
|
// initial state
|
||||||
initState,
|
initState,
|
||||||
// initial locale code
|
// initial locale code
|
||||||
|
@ -440,9 +444,28 @@ function triggerUi () {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the browser popup for user confirmation of watchAsset
|
||||||
|
* then it waits until user interact with the UI
|
||||||
|
*/
|
||||||
|
function showWatchAssetUi () {
|
||||||
|
triggerUi()
|
||||||
|
return new Promise(
|
||||||
|
(resolve) => {
|
||||||
|
var interval = setInterval(() => {
|
||||||
|
if (!notificationIsOpen) {
|
||||||
|
clearInterval(interval)
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// On first install, open a window to MetaMask website to how-it-works.
|
// On first install, open a window to MetaMask website to how-it-works.
|
||||||
extension.runtime.onInstalled.addListener(function (details) {
|
extension.runtime.onInstalled.addListener(function (details) {
|
||||||
if ((details.reason === 'install') && (!METAMASK_DEBUG)) {
|
if ((details.reason === 'install') && (!METAMASK_DEBUG)) {
|
||||||
extension.tabs.create({url: 'https://metamask.io/#how-it-works'})
|
extension.tabs.create({url: 'https://metamask.io/#how-it-works'})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ const LocalMessageDuplexStream = require('post-message-stream')
|
||||||
const PongStream = require('ping-pong-stream/pong')
|
const PongStream = require('ping-pong-stream/pong')
|
||||||
const ObjectMultiplex = require('obj-multiplex')
|
const ObjectMultiplex = require('obj-multiplex')
|
||||||
const extension = require('extensionizer')
|
const extension = require('extensionizer')
|
||||||
const PortStream = require('./lib/port-stream.js')
|
const PortStream = require('extension-port-stream')
|
||||||
|
|
||||||
const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString()
|
const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString()
|
||||||
const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n'
|
const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n'
|
||||||
|
|
|
@ -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')
|
||||||
|
|
||||||
|
|
|
@ -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 }
|
||||||
|
}
|
|
@ -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 }
|
||||||
|
}
|
|
@ -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 }
|
||||||
|
}
|
|
@ -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()
|
|
||||||
}
|
}
|
||||||
// override block tracler
|
|
||||||
provider._blockTracker = createEventEmitterProxy(provider._blockTracker, blockTrackerHandlers)
|
_setProviderAndBlockTracker ({ provider, blockTracker }) {
|
||||||
// set as new provider
|
// update or intialize proxies
|
||||||
|
if (this._providerProxy) {
|
||||||
|
this._providerProxy.setTarget(provider)
|
||||||
|
} else {
|
||||||
|
this._providerProxy = createSwappableProxy(provider)
|
||||||
|
}
|
||||||
|
if (this._blockTrackerProxy) {
|
||||||
|
this._blockTrackerProxy.setTarget(blockTracker)
|
||||||
|
} else {
|
||||||
|
this._blockTrackerProxy = createEventEmitterProxy(blockTracker, { eventFilter: 'skipInternal' })
|
||||||
|
}
|
||||||
|
// set new provider and blockTracker
|
||||||
this._provider = provider
|
this._provider = provider
|
||||||
this._proxy.setTarget(provider)
|
this._blockTracker = blockTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
_logBlock (block) {
|
_logBlock (block) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const normalizeAddress = require('eth-sig-util').normalize
|
const normalizeAddress = require('eth-sig-util').normalize
|
||||||
|
const { isValidAddress } = require('ethereumjs-util')
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ class PreferencesController {
|
||||||
* @property {string} store.currentAccountTab Indicates the selected tab in the ui
|
* @property {string} store.currentAccountTab Indicates the selected tab in the ui
|
||||||
* @property {array} store.tokens The tokens the user wants display in their token lists
|
* @property {array} store.tokens The tokens the user wants display in their token lists
|
||||||
* @property {object} store.accountTokens The tokens stored per account and then per network type
|
* @property {object} store.accountTokens The tokens stored per account and then per network type
|
||||||
|
* @property {object} store.assetImages Contains assets objects related to assets added
|
||||||
* @property {boolean} store.useBlockie The users preference for blockie identicons within the UI
|
* @property {boolean} store.useBlockie The users preference for blockie identicons within the UI
|
||||||
* @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the
|
* @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the
|
||||||
* user wishes to see that feature
|
* user wishes to see that feature
|
||||||
|
@ -26,21 +28,42 @@ class PreferencesController {
|
||||||
frequentRpcList: [],
|
frequentRpcList: [],
|
||||||
currentAccountTab: 'history',
|
currentAccountTab: 'history',
|
||||||
accountTokens: {},
|
accountTokens: {},
|
||||||
|
assetImages: {},
|
||||||
tokens: [],
|
tokens: [],
|
||||||
|
suggestedTokens: {},
|
||||||
useBlockie: false,
|
useBlockie: false,
|
||||||
featureFlags: {},
|
featureFlags: {},
|
||||||
currentLocale: opts.initLangCode,
|
currentLocale: opts.initLangCode,
|
||||||
identities: {},
|
identities: {},
|
||||||
lostIdentities: {},
|
lostIdentities: {},
|
||||||
|
seedWords: null,
|
||||||
|
forgottenPassword: false,
|
||||||
}, opts.initState)
|
}, opts.initState)
|
||||||
|
|
||||||
this.diagnostics = opts.diagnostics
|
this.diagnostics = opts.diagnostics
|
||||||
this.network = opts.network
|
this.network = opts.network
|
||||||
this.store = new ObservableStore(initState)
|
this.store = new ObservableStore(initState)
|
||||||
|
this.showWatchAssetUi = opts.showWatchAssetUi
|
||||||
this._subscribeProviderType()
|
this._subscribeProviderType()
|
||||||
}
|
}
|
||||||
// PUBLIC METHODS
|
// PUBLIC METHODS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@code forgottenPassword} state property
|
||||||
|
* @param {boolean} forgottenPassword whether or not the user has forgotten their password
|
||||||
|
*/
|
||||||
|
setPasswordForgotten (forgottenPassword) {
|
||||||
|
this.store.updateState({ forgottenPassword })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@code seedWords} seed words
|
||||||
|
* @param {string|null} seedWords the seed words
|
||||||
|
*/
|
||||||
|
setSeedWords (seedWords) {
|
||||||
|
this.store.updateState({ seedWords })
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setter for the `useBlockie` property
|
* Setter for the `useBlockie` property
|
||||||
*
|
*
|
||||||
|
@ -51,6 +74,53 @@ class PreferencesController {
|
||||||
this.store.updateState({ useBlockie: val })
|
this.store.updateState({ useBlockie: val })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSuggestedTokens () {
|
||||||
|
return this.store.getState().suggestedTokens
|
||||||
|
}
|
||||||
|
|
||||||
|
getAssetImages () {
|
||||||
|
return this.store.getState().assetImages
|
||||||
|
}
|
||||||
|
|
||||||
|
addSuggestedERC20Asset (tokenOpts) {
|
||||||
|
this._validateERC20AssetParams(tokenOpts)
|
||||||
|
const suggested = this.getSuggestedTokens()
|
||||||
|
const { rawAddress, symbol, decimals, image } = tokenOpts
|
||||||
|
const address = normalizeAddress(rawAddress)
|
||||||
|
const newEntry = { address, symbol, decimals, image }
|
||||||
|
suggested[address] = newEntry
|
||||||
|
this.store.updateState({ suggestedTokens: suggested })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RPC engine middleware for requesting new asset added
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @param {Function} - next
|
||||||
|
* @param {Function} - end
|
||||||
|
*/
|
||||||
|
async requestWatchAsset (req, res, next, end) {
|
||||||
|
if (req.method === 'metamask_watchAsset') {
|
||||||
|
const { type, options } = req.params
|
||||||
|
switch (type) {
|
||||||
|
case 'ERC20':
|
||||||
|
const result = await this._handleWatchAssetERC20(options)
|
||||||
|
if (result instanceof Error) {
|
||||||
|
end(result)
|
||||||
|
} else {
|
||||||
|
res.result = result
|
||||||
|
end()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
end(new Error(`Asset of type ${type} not supported`))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Getter for the `useBlockie` property
|
* Getter for the `useBlockie` property
|
||||||
*
|
*
|
||||||
|
@ -186,6 +256,13 @@ class PreferencesController {
|
||||||
return selected
|
return selected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeSuggestedTokens () {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.store.updateState({ suggestedTokens: {} })
|
||||||
|
resolve({})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setter for the `selectedAddress` property
|
* Setter for the `selectedAddress` property
|
||||||
*
|
*
|
||||||
|
@ -232,11 +309,11 @@ class PreferencesController {
|
||||||
* @returns {Promise<array>} Promises the new array of AddedToken objects.
|
* @returns {Promise<array>} Promises the new array of AddedToken objects.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async addToken (rawAddress, symbol, decimals) {
|
async addToken (rawAddress, symbol, decimals, image) {
|
||||||
const address = normalizeAddress(rawAddress)
|
const address = normalizeAddress(rawAddress)
|
||||||
const newEntry = { address, symbol, decimals }
|
const newEntry = { address, symbol, decimals }
|
||||||
|
|
||||||
const tokens = this.store.getState().tokens
|
const tokens = this.store.getState().tokens
|
||||||
|
const assetImages = this.getAssetImages()
|
||||||
const previousEntry = tokens.find((token, index) => {
|
const previousEntry = tokens.find((token, index) => {
|
||||||
return token.address === address
|
return token.address === address
|
||||||
})
|
})
|
||||||
|
@ -247,7 +324,8 @@ class PreferencesController {
|
||||||
} else {
|
} else {
|
||||||
tokens.push(newEntry)
|
tokens.push(newEntry)
|
||||||
}
|
}
|
||||||
this._updateAccountTokens(tokens)
|
assetImages[address] = image
|
||||||
|
this._updateAccountTokens(tokens, assetImages)
|
||||||
return Promise.resolve(tokens)
|
return Promise.resolve(tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,8 +338,10 @@ class PreferencesController {
|
||||||
*/
|
*/
|
||||||
removeToken (rawAddress) {
|
removeToken (rawAddress) {
|
||||||
const tokens = this.store.getState().tokens
|
const tokens = this.store.getState().tokens
|
||||||
|
const assetImages = this.getAssetImages()
|
||||||
const updatedTokens = tokens.filter(token => token.address !== rawAddress)
|
const updatedTokens = tokens.filter(token => token.address !== rawAddress)
|
||||||
this._updateAccountTokens(updatedTokens)
|
delete assetImages[rawAddress]
|
||||||
|
this._updateAccountTokens(updatedTokens, assetImages)
|
||||||
return Promise.resolve(updatedTokens)
|
return Promise.resolve(updatedTokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,7 +402,7 @@ class PreferencesController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an updated rpcList based on the passed url and the current list.
|
* Returns an updated rpcList based on the passed url and the current list.
|
||||||
* The returned list will have a max length of 2. If the _url currently exists it the list, it will be moved to the
|
* The returned list will have a max length of 3. If the _url currently exists it the list, it will be moved to the
|
||||||
* end of the list. The current list is modified and returned as a promise.
|
* end of the list. The current list is modified and returned as a promise.
|
||||||
*
|
*
|
||||||
* @param {string} _url The rpc url to add to the frequentRpcList.
|
* @param {string} _url The rpc url to add to the frequentRpcList.
|
||||||
|
@ -338,7 +418,7 @@ class PreferencesController {
|
||||||
if (_url !== 'http://localhost:8545') {
|
if (_url !== 'http://localhost:8545') {
|
||||||
rpcList.push(_url)
|
rpcList.push(_url)
|
||||||
}
|
}
|
||||||
if (rpcList.length > 2) {
|
if (rpcList.length > 3) {
|
||||||
rpcList.shift()
|
rpcList.shift()
|
||||||
}
|
}
|
||||||
return Promise.resolve(rpcList)
|
return Promise.resolve(rpcList)
|
||||||
|
@ -387,6 +467,7 @@ class PreferencesController {
|
||||||
//
|
//
|
||||||
// PRIVATE METHODS
|
// PRIVATE METHODS
|
||||||
//
|
//
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscription to network provider type.
|
* Subscription to network provider type.
|
||||||
*
|
*
|
||||||
|
@ -405,10 +486,10 @@ class PreferencesController {
|
||||||
* @param {array} tokens Array of tokens to be updated.
|
* @param {array} tokens Array of tokens to be updated.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_updateAccountTokens (tokens) {
|
_updateAccountTokens (tokens, assetImages) {
|
||||||
const { accountTokens, providerType, selectedAddress } = this._getTokenRelatedStates()
|
const { accountTokens, providerType, selectedAddress } = this._getTokenRelatedStates()
|
||||||
accountTokens[selectedAddress][providerType] = tokens
|
accountTokens[selectedAddress][providerType] = tokens
|
||||||
this.store.updateState({ accountTokens, tokens })
|
this.store.updateState({ accountTokens, tokens, assetImages })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -438,6 +519,47 @@ class PreferencesController {
|
||||||
const tokens = accountTokens[selectedAddress][providerType]
|
const tokens = accountTokens[selectedAddress][providerType]
|
||||||
return { tokens, accountTokens, providerType, selectedAddress }
|
return { tokens, accountTokens, providerType, selectedAddress }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the suggestion of an ERC20 asset through `watchAsset`
|
||||||
|
* *
|
||||||
|
* @param {Promise} promise Promise according to addition of ERC20 token
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
async _handleWatchAssetERC20 (options) {
|
||||||
|
const { address, symbol, decimals, image } = options
|
||||||
|
const rawAddress = address
|
||||||
|
try {
|
||||||
|
this._validateERC20AssetParams({ rawAddress, symbol, decimals })
|
||||||
|
} catch (err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
const tokenOpts = { rawAddress, decimals, symbol, image }
|
||||||
|
this.addSuggestedERC20Asset(tokenOpts)
|
||||||
|
return this.showWatchAssetUi().then(() => {
|
||||||
|
const tokenAddresses = this.getTokens().filter(token => token.address === normalizeAddress(rawAddress))
|
||||||
|
return tokenAddresses.length > 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that the passed options for suggested token have all required properties.
|
||||||
|
*
|
||||||
|
* @param {Object} opts The options object to validate
|
||||||
|
* @throws {string} Throw a custom error indicating that address, symbol and/or decimals
|
||||||
|
* doesn't fulfill requirements
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_validateERC20AssetParams (opts) {
|
||||||
|
const { rawAddress, symbol, decimals } = opts
|
||||||
|
if (!rawAddress || !symbol || !decimals) throw new Error(`Cannot suggest token without address, symbol, and decimals`)
|
||||||
|
if (!(symbol.length < 6)) throw new Error(`Invalid symbol ${symbol} more than five characters`)
|
||||||
|
const numDecimals = parseInt(decimals, 10)
|
||||||
|
if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) {
|
||||||
|
throw new Error(`Invalid decimals ${decimals} must be at least 0, and not over 36`)
|
||||||
|
}
|
||||||
|
if (!isValidAddress(rawAddress)) throw new Error(`Invalid address ${rawAddress}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PreferencesController
|
module.exports = PreferencesController
|
||||||
|
|
|
@ -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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
const TRANSACTION_TYPE_CANCEL = 'cancel'
|
||||||
|
const TRANSACTION_TYPE_RETRY = 'retry'
|
||||||
|
const TRANSACTION_TYPE_STANDARD = 'standard'
|
||||||
|
|
||||||
|
const TRANSACTION_STATUS_APPROVED = 'approved'
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
TRANSACTION_TYPE_CANCEL,
|
||||||
|
TRANSACTION_TYPE_RETRY,
|
||||||
|
TRANSACTION_TYPE_STANDARD,
|
||||||
|
TRANSACTION_STATUS_APPROVED,
|
||||||
|
}
|
|
@ -11,6 +11,14 @@ const txUtils = require('./lib/util')
|
||||||
const cleanErrorStack = require('../../lib/cleanErrorStack')
|
const cleanErrorStack = require('../../lib/cleanErrorStack')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
const recipientBlacklistChecker = require('./lib/recipient-blacklist-checker')
|
const recipientBlacklistChecker = require('./lib/recipient-blacklist-checker')
|
||||||
|
const {
|
||||||
|
TRANSACTION_TYPE_CANCEL,
|
||||||
|
TRANSACTION_TYPE_RETRY,
|
||||||
|
TRANSACTION_TYPE_STANDARD,
|
||||||
|
TRANSACTION_STATUS_APPROVED,
|
||||||
|
} = require('./enums')
|
||||||
|
|
||||||
|
const { hexToBn, bnToHex } = require('../../lib/util')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Transaction Controller is an aggregate of sub-controllers and trackers
|
Transaction Controller is an aggregate of sub-controllers and trackers
|
||||||
|
@ -65,6 +73,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 +87,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()
|
||||||
|
@ -155,7 +168,10 @@ class TransactionController extends EventEmitter {
|
||||||
const normalizedTxParams = txUtils.normalizeTxParams(txParams)
|
const normalizedTxParams = txUtils.normalizeTxParams(txParams)
|
||||||
txUtils.validateTxParams(normalizedTxParams)
|
txUtils.validateTxParams(normalizedTxParams)
|
||||||
// construct txMeta
|
// construct txMeta
|
||||||
let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams })
|
let txMeta = this.txStateManager.generateTxMeta({
|
||||||
|
txParams: normalizedTxParams,
|
||||||
|
type: TRANSACTION_TYPE_STANDARD,
|
||||||
|
})
|
||||||
this.addTx(txMeta)
|
this.addTx(txMeta)
|
||||||
this.emit('newUnapprovedTx', txMeta)
|
this.emit('newUnapprovedTx', txMeta)
|
||||||
|
|
||||||
|
@ -209,12 +225,46 @@ class TransactionController extends EventEmitter {
|
||||||
txParams: originalTxMeta.txParams,
|
txParams: originalTxMeta.txParams,
|
||||||
lastGasPrice,
|
lastGasPrice,
|
||||||
loadingDefaults: false,
|
loadingDefaults: false,
|
||||||
|
type: TRANSACTION_TYPE_RETRY,
|
||||||
})
|
})
|
||||||
this.addTx(txMeta)
|
this.addTx(txMeta)
|
||||||
this.emit('newUnapprovedTx', txMeta)
|
this.emit('newUnapprovedTx', txMeta)
|
||||||
return txMeta
|
return txMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new approved transaction to attempt to cancel a previously submitted transaction. The
|
||||||
|
* new transaction contains the same nonce as the previous, is a basic ETH transfer of 0x value to
|
||||||
|
* the sender's address, and has a higher gasPrice than that of the previous transaction.
|
||||||
|
* @param {number} originalTxId - the id of the txMeta that you want to attempt to cancel
|
||||||
|
* @param {string=} customGasPrice - the hex value to use for the cancel transaction
|
||||||
|
* @returns {txMeta}
|
||||||
|
*/
|
||||||
|
async createCancelTransaction (originalTxId, customGasPrice) {
|
||||||
|
const originalTxMeta = this.txStateManager.getTx(originalTxId)
|
||||||
|
const { txParams } = originalTxMeta
|
||||||
|
const { gasPrice: lastGasPrice, from, nonce } = txParams
|
||||||
|
const newGasPrice = customGasPrice || bnToHex(hexToBn(lastGasPrice).mul(1.1))
|
||||||
|
const newTxMeta = this.txStateManager.generateTxMeta({
|
||||||
|
txParams: {
|
||||||
|
from,
|
||||||
|
to: from,
|
||||||
|
nonce,
|
||||||
|
gas: '0x5208',
|
||||||
|
value: '0x0',
|
||||||
|
gasPrice: newGasPrice,
|
||||||
|
},
|
||||||
|
lastGasPrice,
|
||||||
|
loadingDefaults: false,
|
||||||
|
status: TRANSACTION_STATUS_APPROVED,
|
||||||
|
type: TRANSACTION_TYPE_CANCEL,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.addTx(newTxMeta)
|
||||||
|
await this.approveTransaction(newTxMeta.id)
|
||||||
|
return newTxMeta
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
updates the txMeta in the txStateManager
|
updates the txMeta in the txStateManager
|
||||||
@param txMeta {Object} - the updated txMeta
|
@param txMeta {Object} - the updated txMeta
|
||||||
|
@ -311,6 +361,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 +409,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
|
||||||
|
@ -375,7 +438,7 @@ class TransactionController extends EventEmitter {
|
||||||
})
|
})
|
||||||
|
|
||||||
this.txStateManager.getFilteredTxList({
|
this.txStateManager.getFilteredTxList({
|
||||||
status: 'approved',
|
status: TRANSACTION_STATUS_APPROVED,
|
||||||
}).forEach((txMeta) => {
|
}).forEach((txMeta) => {
|
||||||
const txSignError = new Error('Transaction found as "approved" during boot - possibly stuck during signing')
|
const txSignError = new Error('Transaction found as "approved" during boot - possibly stuck during signing')
|
||||||
this.txStateManager.setTxStatusFailed(txMeta.id, txSignError)
|
this.txStateManager.setTxStatusFailed(txMeta.id, txSignError)
|
||||||
|
@ -386,14 +449,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 +468,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 +491,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
|
|
||||||
}
|
}
|
||||||
|
nonceGlobalLock.releaseLock()
|
||||||
|
|
||||||
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
|
|
||||||
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)
|
||||||
|
|
|
@ -353,6 +353,7 @@ class TransactionStateManager extends EventEmitter {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
txMeta.err = {
|
txMeta.err = {
|
||||||
message: err.toString(),
|
message: err.toString(),
|
||||||
|
rpc: err.value,
|
||||||
stack: err.stack,
|
stack: err.stack,
|
||||||
}
|
}
|
||||||
this.updateTx(txMeta)
|
this.updateTx(txMeta)
|
||||||
|
|
|
@ -4,7 +4,7 @@ require('web3/dist/web3.min.js')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
const LocalMessageDuplexStream = require('post-message-stream')
|
const LocalMessageDuplexStream = require('post-message-stream')
|
||||||
const setupDappAutoReload = require('./lib/auto-reload.js')
|
const setupDappAutoReload = require('./lib/auto-reload.js')
|
||||||
const MetamaskInpageProvider = require('./lib/inpage-provider.js')
|
const MetamaskInpageProvider = require('metamask-inpage-provider')
|
||||||
restoreContextAfterImports()
|
restoreContextAfterImports()
|
||||||
|
|
||||||
log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
|
log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
|
||||||
|
@ -22,6 +22,25 @@ var metamaskStream = new LocalMessageDuplexStream({
|
||||||
// compose the inpage provider
|
// compose the inpage provider
|
||||||
var inpageProvider = new MetamaskInpageProvider(metamaskStream)
|
var inpageProvider = new MetamaskInpageProvider(metamaskStream)
|
||||||
|
|
||||||
|
// Augment the provider with its enable method
|
||||||
|
inpageProvider.enable = function (options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (options.mockRejection) {
|
||||||
|
reject('User rejected account access')
|
||||||
|
} else {
|
||||||
|
inpageProvider.sendAsync({ method: 'eth_accounts', params: [] }, (error, response) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
resolve(response.result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
window.ethereum = inpageProvider
|
||||||
|
|
||||||
//
|
//
|
||||||
// setup web3
|
// setup web3
|
||||||
//
|
//
|
||||||
|
|
|
@ -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,29 @@ 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
|
|
||||||
this._blockTracker.on('block', this._updateForBlock.bind(this))
|
|
||||||
// blockTracker.currentBlock may be null
|
// blockTracker.currentBlock may be null
|
||||||
this._currentBlockNumber = this._blockTracker.currentBlock
|
this._currentBlockNumber = this._blockTracker.getCurrentBlock()
|
||||||
|
this._blockTracker.once('latest', blockNumber => {
|
||||||
|
this._currentBlockNumber = blockNumber
|
||||||
|
})
|
||||||
|
// bind function for easier listener syntax
|
||||||
|
this._updateForBlock = this._updateForBlock.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
start () {
|
||||||
|
// remove first to avoid double add
|
||||||
|
this._blockTracker.removeListener('latest', this._updateForBlock)
|
||||||
|
// add listener
|
||||||
|
this._blockTracker.addListener('latest', this._updateForBlock)
|
||||||
|
// fetch account balances
|
||||||
|
this._updateAccounts()
|
||||||
|
}
|
||||||
|
|
||||||
|
stop () {
|
||||||
|
// remove listener
|
||||||
|
this._blockTracker.removeListener('latest', this._updateForBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,49 +81,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
|
||||||
|
// add initial state for addresses
|
||||||
|
addresses.forEach(address => {
|
||||||
accounts[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
|
||||||
|
// remove each state object
|
||||||
|
addresses.forEach(address => {
|
||||||
delete accounts[address]
|
delete accounts[address]
|
||||||
|
})
|
||||||
|
// save accounts state
|
||||||
this.store.updateState({ accounts })
|
this.store.updateState({ accounts })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,72 +140,57 @@ 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
|
||||||
|
const { accounts } = this.store.getState()
|
||||||
// only populate if the entry is still present
|
// only populate if the entry is still present
|
||||||
if (accounts[address]) {
|
if (!accounts[address]) return
|
||||||
accounts[address] = result
|
accounts[address] = result
|
||||||
this.store.updateState({ accounts })
|
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,254 +0,0 @@
|
||||||
const ethUtil = require('ethereumjs-util')
|
|
||||||
const normalize = require('eth-sig-util').normalize
|
|
||||||
const {
|
|
||||||
MAINNET_RPC_URL,
|
|
||||||
ROPSTEN_RPC_URL,
|
|
||||||
KOVAN_RPC_URL,
|
|
||||||
RINKEBY_RPC_URL,
|
|
||||||
} = require('../controllers/network/enums')
|
|
||||||
|
|
||||||
/* The config-manager is a convenience object
|
|
||||||
* wrapping a pojo-migrator.
|
|
||||||
*
|
|
||||||
* It exists mostly to allow the creation of
|
|
||||||
* convenience methods to access and persist
|
|
||||||
* particular portions of the state.
|
|
||||||
*/
|
|
||||||
module.exports = ConfigManager
|
|
||||||
function ConfigManager (opts) {
|
|
||||||
// ConfigManager is observable and will emit updates
|
|
||||||
this._subs = []
|
|
||||||
this.store = opts.store
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.setConfig = function (config) {
|
|
||||||
var data = this.getData()
|
|
||||||
data.config = config
|
|
||||||
this.setData(data)
|
|
||||||
this._emitUpdates(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.getConfig = function () {
|
|
||||||
var data = this.getData()
|
|
||||||
return data.config
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.setData = function (data) {
|
|
||||||
this.store.putState(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.getData = function () {
|
|
||||||
return this.store.getState()
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.setPasswordForgotten = function (passwordForgottenState) {
|
|
||||||
const data = this.getData()
|
|
||||||
data.forgottenPassword = passwordForgottenState
|
|
||||||
this.setData(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.getPasswordForgotten = function (passwordForgottenState) {
|
|
||||||
const data = this.getData()
|
|
||||||
return data.forgottenPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.setWallet = function (wallet) {
|
|
||||||
var data = this.getData()
|
|
||||||
data.wallet = wallet
|
|
||||||
this.setData(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.setVault = function (encryptedString) {
|
|
||||||
var data = this.getData()
|
|
||||||
data.vault = encryptedString
|
|
||||||
this.setData(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.getVault = function () {
|
|
||||||
var data = this.getData()
|
|
||||||
return data.vault
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.getKeychains = function () {
|
|
||||||
return this.getData().keychains || []
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.setKeychains = function (keychains) {
|
|
||||||
var data = this.getData()
|
|
||||||
data.keychains = keychains
|
|
||||||
this.setData(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.getSelectedAccount = function () {
|
|
||||||
var config = this.getConfig()
|
|
||||||
return config.selectedAccount
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.setSelectedAccount = function (address) {
|
|
||||||
var config = this.getConfig()
|
|
||||||
config.selectedAccount = ethUtil.addHexPrefix(address)
|
|
||||||
this.setConfig(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.getWallet = function () {
|
|
||||||
return this.getData().wallet
|
|
||||||
}
|
|
||||||
|
|
||||||
// Takes a boolean
|
|
||||||
ConfigManager.prototype.setShowSeedWords = function (should) {
|
|
||||||
var data = this.getData()
|
|
||||||
data.showSeedWords = should
|
|
||||||
this.setData(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ConfigManager.prototype.getShouldShowSeedWords = function () {
|
|
||||||
var data = this.getData()
|
|
||||||
return data.showSeedWords
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.setSeedWords = function (words) {
|
|
||||||
var data = this.getData()
|
|
||||||
data.seedWords = words
|
|
||||||
this.setData(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.getSeedWords = function () {
|
|
||||||
var data = this.getData()
|
|
||||||
return data.seedWords
|
|
||||||
}
|
|
||||||
ConfigManager.prototype.setRpcTarget = function (rpcUrl) {
|
|
||||||
var config = this.getConfig()
|
|
||||||
config.provider = {
|
|
||||||
type: 'rpc',
|
|
||||||
rpcTarget: rpcUrl,
|
|
||||||
}
|
|
||||||
this.setConfig(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.setProviderType = function (type) {
|
|
||||||
var config = this.getConfig()
|
|
||||||
config.provider = {
|
|
||||||
type: type,
|
|
||||||
}
|
|
||||||
this.setConfig(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.useEtherscanProvider = function () {
|
|
||||||
var config = this.getConfig()
|
|
||||||
config.provider = {
|
|
||||||
type: 'etherscan',
|
|
||||||
}
|
|
||||||
this.setConfig(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.getProvider = function () {
|
|
||||||
var config = this.getConfig()
|
|
||||||
return config.provider
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.getCurrentRpcAddress = function () {
|
|
||||||
var provider = this.getProvider()
|
|
||||||
if (!provider) return null
|
|
||||||
switch (provider.type) {
|
|
||||||
|
|
||||||
case 'mainnet':
|
|
||||||
return MAINNET_RPC_URL
|
|
||||||
|
|
||||||
case 'ropsten':
|
|
||||||
return ROPSTEN_RPC_URL
|
|
||||||
|
|
||||||
case 'kovan':
|
|
||||||
return KOVAN_RPC_URL
|
|
||||||
|
|
||||||
case 'rinkeby':
|
|
||||||
return RINKEBY_RPC_URL
|
|
||||||
|
|
||||||
default:
|
|
||||||
return provider && provider.rpcTarget ? provider.rpcTarget : RINKEBY_RPC_URL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Tx
|
|
||||||
//
|
|
||||||
|
|
||||||
ConfigManager.prototype.getTxList = function () {
|
|
||||||
var data = this.getData()
|
|
||||||
if (data.transactions !== undefined) {
|
|
||||||
return data.transactions
|
|
||||||
} else {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.setTxList = function (txList) {
|
|
||||||
var data = this.getData()
|
|
||||||
data.transactions = txList
|
|
||||||
this.setData(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// wallet nickname methods
|
|
||||||
|
|
||||||
ConfigManager.prototype.getWalletNicknames = function () {
|
|
||||||
var data = this.getData()
|
|
||||||
const nicknames = ('walletNicknames' in data) ? data.walletNicknames : {}
|
|
||||||
return nicknames
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.nicknameForWallet = function (account) {
|
|
||||||
const address = normalize(account)
|
|
||||||
const nicknames = this.getWalletNicknames()
|
|
||||||
return nicknames[address]
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.setNicknameForWallet = function (account, nickname) {
|
|
||||||
const address = normalize(account)
|
|
||||||
const nicknames = this.getWalletNicknames()
|
|
||||||
nicknames[address] = nickname
|
|
||||||
var data = this.getData()
|
|
||||||
data.walletNicknames = nicknames
|
|
||||||
this.setData(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// observable
|
|
||||||
|
|
||||||
ConfigManager.prototype.getSalt = function () {
|
|
||||||
var data = this.getData()
|
|
||||||
return data.salt
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.setSalt = function (salt) {
|
|
||||||
var data = this.getData()
|
|
||||||
data.salt = salt
|
|
||||||
this.setData(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.subscribe = function (fn) {
|
|
||||||
this._subs.push(fn)
|
|
||||||
var unsubscribe = this.unsubscribe.bind(this, fn)
|
|
||||||
return unsubscribe
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.unsubscribe = function (fn) {
|
|
||||||
var index = this._subs.indexOf(fn)
|
|
||||||
if (index !== -1) this._subs.splice(index, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype._emitUpdates = function (state) {
|
|
||||||
this._subs.forEach(function (handler) {
|
|
||||||
handler(state)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.setLostAccounts = function (lostAccounts) {
|
|
||||||
var data = this.getData()
|
|
||||||
data.lostAccounts = lostAccounts
|
|
||||||
this.setData(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.getLostAccounts = function () {
|
|
||||||
var data = this.getData()
|
|
||||||
return data.lostAccounts || []
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
const log = require('loglevel')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON-RPC error object
|
|
||||||
*
|
|
||||||
* @typedef {Object} RpcError
|
|
||||||
* @property {number} code - Indicates the error type that occurred
|
|
||||||
* @property {Object} [data] - Contains additional information about the error
|
|
||||||
* @property {string} [message] - Short description of the error
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Middleware configuration object
|
|
||||||
*
|
|
||||||
* @typedef {Object} MiddlewareConfig
|
|
||||||
* @property {boolean} [override] - Use RPC_ERRORS message in place of provider message
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map of standard and non-standard RPC error codes to messages
|
|
||||||
*/
|
|
||||||
const RPC_ERRORS = {
|
|
||||||
1: 'An unauthorized action was attempted.',
|
|
||||||
2: 'A disallowed action was attempted.',
|
|
||||||
3: 'An execution error occurred.',
|
|
||||||
[-32600]: 'The JSON sent is not a valid Request object.',
|
|
||||||
[-32601]: 'The method does not exist / is not available.',
|
|
||||||
[-32602]: 'Invalid method parameter(s).',
|
|
||||||
[-32603]: 'Internal JSON-RPC error.',
|
|
||||||
[-32700]: 'Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.',
|
|
||||||
internal: 'Internal server error.',
|
|
||||||
unknown: 'Unknown JSON-RPC error.',
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modifies a JSON-RPC error object in-place to add a human-readable message,
|
|
||||||
* optionally overriding any provider-supplied message
|
|
||||||
*
|
|
||||||
* @param {RpcError} error - JSON-RPC error object
|
|
||||||
* @param {boolean} override - Use RPC_ERRORS message in place of provider message
|
|
||||||
*/
|
|
||||||
function sanitizeRPCError (error, override) {
|
|
||||||
if (error.message && !override) { return error }
|
|
||||||
const message = error.code > -31099 && error.code < -32100 ? RPC_ERRORS.internal : RPC_ERRORS[error.code]
|
|
||||||
error.message = message || RPC_ERRORS.unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* json-rpc-engine middleware that both logs standard and non-standard error
|
|
||||||
* messages and ends middleware stack traversal if an error is encountered
|
|
||||||
*
|
|
||||||
* @param {MiddlewareConfig} [config={override:true}] - Middleware configuration
|
|
||||||
* @returns {Function} json-rpc-engine middleware function
|
|
||||||
*/
|
|
||||||
function createErrorMiddleware ({ override = true } = {}) {
|
|
||||||
return (req, res, next) => {
|
|
||||||
next(done => {
|
|
||||||
const { error } = res
|
|
||||||
if (!error) { return done() }
|
|
||||||
sanitizeRPCError(error)
|
|
||||||
log.error(`MetaMask - RPC Error: ${error.message}`, error)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = createErrorMiddleware
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,125 +0,0 @@
|
||||||
const pump = require('pump')
|
|
||||||
const RpcEngine = require('json-rpc-engine')
|
|
||||||
const createErrorMiddleware = require('./createErrorMiddleware')
|
|
||||||
const createIdRemapMiddleware = require('json-rpc-engine/src/idRemapMiddleware')
|
|
||||||
const createStreamMiddleware = require('json-rpc-middleware-stream')
|
|
||||||
const LocalStorageStore = require('obs-store')
|
|
||||||
const asStream = require('obs-store/lib/asStream')
|
|
||||||
const ObjectMultiplex = require('obj-multiplex')
|
|
||||||
|
|
||||||
module.exports = MetamaskInpageProvider
|
|
||||||
|
|
||||||
function MetamaskInpageProvider (connectionStream) {
|
|
||||||
const self = this
|
|
||||||
|
|
||||||
// setup connectionStream multiplexing
|
|
||||||
const mux = self.mux = new ObjectMultiplex()
|
|
||||||
pump(
|
|
||||||
connectionStream,
|
|
||||||
mux,
|
|
||||||
connectionStream,
|
|
||||||
(err) => logStreamDisconnectWarning('MetaMask', err)
|
|
||||||
)
|
|
||||||
|
|
||||||
// subscribe to metamask public config (one-way)
|
|
||||||
self.publicConfigStore = new LocalStorageStore({ storageKey: 'MetaMask-Config' })
|
|
||||||
|
|
||||||
pump(
|
|
||||||
mux.createStream('publicConfig'),
|
|
||||||
asStream(self.publicConfigStore),
|
|
||||||
(err) => logStreamDisconnectWarning('MetaMask PublicConfigStore', err)
|
|
||||||
)
|
|
||||||
|
|
||||||
// ignore phishing warning message (handled elsewhere)
|
|
||||||
mux.ignoreStream('phishing')
|
|
||||||
|
|
||||||
// connect to async provider
|
|
||||||
const streamMiddleware = createStreamMiddleware()
|
|
||||||
pump(
|
|
||||||
streamMiddleware.stream,
|
|
||||||
mux.createStream('provider'),
|
|
||||||
streamMiddleware.stream,
|
|
||||||
(err) => logStreamDisconnectWarning('MetaMask RpcProvider', err)
|
|
||||||
)
|
|
||||||
|
|
||||||
// handle sendAsync requests via dapp-side rpc engine
|
|
||||||
const rpcEngine = new RpcEngine()
|
|
||||||
rpcEngine.push(createIdRemapMiddleware())
|
|
||||||
rpcEngine.push(createErrorMiddleware())
|
|
||||||
rpcEngine.push(streamMiddleware)
|
|
||||||
self.rpcEngine = rpcEngine
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle sendAsync requests via asyncProvider
|
|
||||||
// also remap ids inbound and outbound
|
|
||||||
MetamaskInpageProvider.prototype.sendAsync = function (payload, cb) {
|
|
||||||
const self = this
|
|
||||||
|
|
||||||
if (payload.method === 'eth_signTypedData') {
|
|
||||||
console.warn('MetaMask: This experimental version of eth_signTypedData will be deprecated in the next release in favor of the standard as defined in EIP-712. See https://git.io/fNzPl for more information on the new standard.')
|
|
||||||
}
|
|
||||||
|
|
||||||
self.rpcEngine.handle(payload, cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
MetamaskInpageProvider.prototype.send = function (payload) {
|
|
||||||
const self = this
|
|
||||||
|
|
||||||
let selectedAddress
|
|
||||||
let result = null
|
|
||||||
switch (payload.method) {
|
|
||||||
|
|
||||||
case 'eth_accounts':
|
|
||||||
// read from localStorage
|
|
||||||
selectedAddress = self.publicConfigStore.getState().selectedAddress
|
|
||||||
result = selectedAddress ? [selectedAddress] : []
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'eth_coinbase':
|
|
||||||
// read from localStorage
|
|
||||||
selectedAddress = self.publicConfigStore.getState().selectedAddress
|
|
||||||
result = selectedAddress || null
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'eth_uninstallFilter':
|
|
||||||
self.sendAsync(payload, noop)
|
|
||||||
result = true
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'net_version':
|
|
||||||
const networkVersion = self.publicConfigStore.getState().networkVersion
|
|
||||||
result = networkVersion || null
|
|
||||||
break
|
|
||||||
|
|
||||||
// throw not-supported Error
|
|
||||||
default:
|
|
||||||
var link = 'https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#dizzy-all-async---think-of-metamask-as-a-light-client'
|
|
||||||
var message = `The MetaMask Web3 object does not support synchronous methods like ${payload.method} without a callback parameter. See ${link} for details.`
|
|
||||||
throw new Error(message)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// return the result
|
|
||||||
return {
|
|
||||||
id: payload.id,
|
|
||||||
jsonrpc: payload.jsonrpc,
|
|
||||||
result: result,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MetamaskInpageProvider.prototype.isConnected = function () {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
MetamaskInpageProvider.prototype.isMetaMask = true
|
|
||||||
|
|
||||||
// util
|
|
||||||
|
|
||||||
function logStreamDisconnectWarning (remoteLabel, err) {
|
|
||||||
let warningMsg = `MetamaskInpageProvider - lost connection to ${remoteLabel}`
|
|
||||||
if (err) warningMsg += '\n' + err.stack
|
|
||||||
console.warn(warningMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
function noop () {}
|
|
|
@ -34,7 +34,7 @@ module.exports = function (provider) {
|
||||||
return { cancel: true }
|
return { cancel: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension.webRequest.onErrorOccurred.addListener(ipfsContent, {urls: ['*://*.eth/', '*://*.test/']})
|
extension.webRequest.onErrorOccurred.addListener(ipfsContent, {urls: ['*://*.eth/']})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
remove () {
|
remove () {
|
||||||
|
|
|
@ -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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
const Duplex = require('readable-stream').Duplex
|
|
||||||
const inherits = require('util').inherits
|
|
||||||
const noop = function () {}
|
|
||||||
|
|
||||||
module.exports = PortDuplexStream
|
|
||||||
|
|
||||||
inherits(PortDuplexStream, Duplex)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a stream that's both readable and writable.
|
|
||||||
* The stream supports arbitrary objects.
|
|
||||||
*
|
|
||||||
* @class
|
|
||||||
* @param {Object} port Remote Port object
|
|
||||||
*/
|
|
||||||
function PortDuplexStream (port) {
|
|
||||||
Duplex.call(this, {
|
|
||||||
objectMode: true,
|
|
||||||
})
|
|
||||||
this._port = port
|
|
||||||
port.onMessage.addListener(this._onMessage.bind(this))
|
|
||||||
port.onDisconnect.addListener(this._onDisconnect.bind(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback triggered when a message is received from
|
|
||||||
* the remote Port associated with this Stream.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {Object} msg - Payload from the onMessage listener of Port
|
|
||||||
*/
|
|
||||||
PortDuplexStream.prototype._onMessage = function (msg) {
|
|
||||||
if (Buffer.isBuffer(msg)) {
|
|
||||||
delete msg._isBuffer
|
|
||||||
var data = new Buffer(msg)
|
|
||||||
this.push(data)
|
|
||||||
} else {
|
|
||||||
this.push(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback triggered when the remote Port
|
|
||||||
* associated with this Stream disconnects.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
PortDuplexStream.prototype._onDisconnect = function () {
|
|
||||||
this.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Explicitly sets read operations to a no-op
|
|
||||||
*/
|
|
||||||
PortDuplexStream.prototype._read = noop
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called internally when data should be written to
|
|
||||||
* this writable stream.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {*} msg Arbitrary object to write
|
|
||||||
* @param {string} encoding Encoding to use when writing payload
|
|
||||||
* @param {Function} cb Called when writing is complete or an error occurs
|
|
||||||
*/
|
|
||||||
PortDuplexStream.prototype._write = function (msg, encoding, cb) {
|
|
||||||
try {
|
|
||||||
if (Buffer.isBuffer(msg)) {
|
|
||||||
var data = msg.toJSON()
|
|
||||||
data._isBuffer = true
|
|
||||||
this._port.postMessage(data)
|
|
||||||
} else {
|
|
||||||
this._port.postMessage(msg)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return cb(new Error('PortDuplexStream - disconnected'))
|
|
||||||
}
|
|
||||||
cb()
|
|
||||||
}
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ const createId = require('./random-id')
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const sigUtil = require('eth-sig-util')
|
const sigUtil = require('eth-sig-util')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
|
const jsonschema = require('jsonschema')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents, and contains data about, an 'eth_signTypedData' type signature request. These are created when a
|
* Represents, and contains data about, an 'eth_signTypedData' type signature request. These are created when a
|
||||||
|
@ -17,7 +18,7 @@ const log = require('loglevel')
|
||||||
* @property {Object} msgParams.from The address that is making the signature request.
|
* @property {Object} msgParams.from The address that is making the signature request.
|
||||||
* @property {string} msgParams.data A hex string conversion of the raw buffer data of the signature request
|
* @property {string} msgParams.data A hex string conversion of the raw buffer data of the signature request
|
||||||
* @property {number} time The epoch time at which the this message was created
|
* @property {number} time The epoch time at which the this message was created
|
||||||
* @property {string} status Indicates whether the signature request is 'unapproved', 'approved', 'signed' or 'rejected'
|
* @property {string} status Indicates whether the signature request is 'unapproved', 'approved', 'signed', 'rejected', or 'errored'
|
||||||
* @property {string} type The json-prc signing method for which a signature request has been made. A 'Message' will
|
* @property {string} type The json-prc signing method for which a signature request has been made. A 'Message' will
|
||||||
* always have a 'eth_signTypedData' type.
|
* always have a 'eth_signTypedData' type.
|
||||||
*
|
*
|
||||||
|
@ -26,17 +27,10 @@ const log = require('loglevel')
|
||||||
module.exports = class TypedMessageManager extends EventEmitter {
|
module.exports = class TypedMessageManager extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
* Controller in charge of managing - storing, adding, removing, updating - TypedMessage.
|
* Controller in charge of managing - storing, adding, removing, updating - TypedMessage.
|
||||||
*
|
|
||||||
* @typedef {Object} TypedMessage
|
|
||||||
* @param {Object} opts @deprecated
|
|
||||||
* @property {Object} memStore The observable store where TypedMessage are saved.
|
|
||||||
* @property {Object} memStore.unapprovedTypedMessages A collection of all TypedMessages in the 'unapproved' state
|
|
||||||
* @property {number} memStore.unapprovedTypedMessagesCount The count of all TypedMessages in this.memStore.unapprobedMsgs
|
|
||||||
* @property {array} messages Holds all messages that have been created by this TypedMessage
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
constructor (opts) {
|
constructor ({ networkController }) {
|
||||||
super()
|
super()
|
||||||
|
this.networkController = networkController
|
||||||
this.memStore = new ObservableStore({
|
this.memStore = new ObservableStore({
|
||||||
unapprovedTypedMessages: {},
|
unapprovedTypedMessages: {},
|
||||||
unapprovedTypedMessagesCount: 0,
|
unapprovedTypedMessagesCount: 0,
|
||||||
|
@ -72,11 +66,43 @@ 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, version) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const msgId = this.addUnapprovedMessage(msgParams, req, version)
|
||||||
|
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.'))
|
||||||
|
case 'errored':
|
||||||
|
return reject(new Error(`MetaMask Message Signature: ${data.error}`))
|
||||||
|
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, version) {
|
||||||
|
msgParams.version = version
|
||||||
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
|
||||||
|
@ -103,6 +129,8 @@ module.exports = class TypedMessageManager extends EventEmitter {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
validateParams (params) {
|
validateParams (params) {
|
||||||
|
switch (params.version) {
|
||||||
|
case 'V1':
|
||||||
assert.equal(typeof params, 'object', 'Params should ben an object.')
|
assert.equal(typeof params, 'object', 'Params should ben an object.')
|
||||||
assert.ok('data' in params, 'Params must include a data field.')
|
assert.ok('data' in params, 'Params must include a data field.')
|
||||||
assert.ok('from' in params, 'Params must include a from field.')
|
assert.ok('from' in params, 'Params must include a from field.')
|
||||||
|
@ -111,6 +139,23 @@ module.exports = class TypedMessageManager extends EventEmitter {
|
||||||
assert.doesNotThrow(() => {
|
assert.doesNotThrow(() => {
|
||||||
sigUtil.typedSignatureHash(params.data)
|
sigUtil.typedSignatureHash(params.data)
|
||||||
}, 'Expected EIP712 typed data')
|
}, 'Expected EIP712 typed data')
|
||||||
|
break
|
||||||
|
case 'V3':
|
||||||
|
let data
|
||||||
|
assert.equal(typeof params, 'object', 'Params should be an object.')
|
||||||
|
assert.ok('data' in params, 'Params must include a data field.')
|
||||||
|
assert.ok('from' in params, 'Params must include a from field.')
|
||||||
|
assert.equal(typeof params.from, 'string', 'From field must be a string.')
|
||||||
|
assert.equal(typeof params.data, 'string', 'Data must be passed as a valid JSON string.')
|
||||||
|
assert.doesNotThrow(() => { data = JSON.parse(params.data) }, 'Data must be passed as a valid JSON string.')
|
||||||
|
const validation = jsonschema.validate(data, sigUtil.TYPED_MESSAGE_SCHEMA)
|
||||||
|
assert.ok(data.primaryType in data.types, `Primary type of "${data.primaryType}" has no type definition.`)
|
||||||
|
assert.equal(validation.errors.length, 0, 'Data must conform to EIP-712 schema. See https://git.io/fNtcx.')
|
||||||
|
const chainId = data.domain.chainId
|
||||||
|
const activeChainId = parseInt(this.networkController.getNetworkState())
|
||||||
|
chainId && assert.equal(chainId, activeChainId, `Provided chainId (${chainId}) must match the active chainId (${activeChainId})`)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -185,6 +230,7 @@ module.exports = class TypedMessageManager extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
prepMsgForSigning (msgParams) {
|
prepMsgForSigning (msgParams) {
|
||||||
delete msgParams.metamaskId
|
delete msgParams.metamaskId
|
||||||
|
delete msgParams.version
|
||||||
return Promise.resolve(msgParams)
|
return Promise.resolve(msgParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,6 +244,19 @@ module.exports = class TypedMessageManager extends EventEmitter {
|
||||||
this._setMsgStatus(msgId, 'rejected')
|
this._setMsgStatus(msgId, 'rejected')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a TypedMessage status to 'errored' via a call to this._setMsgStatus.
|
||||||
|
*
|
||||||
|
* @param {number} msgId The id of the TypedMessage to error
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
errorMessage (msgId, error) {
|
||||||
|
const msg = this.getMsg(msgId)
|
||||||
|
msg.error = error
|
||||||
|
this._updateMsg(msg)
|
||||||
|
this._setMsgStatus(msgId, 'errored')
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// PRIVATE METHODS
|
// PRIVATE METHODS
|
||||||
//
|
//
|
||||||
|
@ -221,7 +280,7 @@ module.exports = class TypedMessageManager extends EventEmitter {
|
||||||
msg.status = status
|
msg.status = status
|
||||||
this._updateMsg(msg)
|
this._updateMsg(msg)
|
||||||
this.emit(`${msgId}:${status}`, msg)
|
this.emit(`${msgId}:${status}`, msg)
|
||||||
if (status === 'rejected' || status === 'signed') {
|
if (status === 'rejected' || status === 'signed' || status === 'errored') {
|
||||||
this.emit(`${msgId}:finished`, msg)
|
this.emit(`${msgId}:finished`, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,7 +127,21 @@ 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,
|
getPlatform,
|
||||||
getStack,
|
getStack,
|
||||||
getEnvironmentType,
|
getEnvironmentType,
|
||||||
|
|
|
@ -36,7 +36,6 @@ const TransactionController = require('./controllers/transactions')
|
||||||
const BalancesController = require('./controllers/computed-balances')
|
const BalancesController = require('./controllers/computed-balances')
|
||||||
const TokenRatesController = require('./controllers/token-rates')
|
const TokenRatesController = require('./controllers/token-rates')
|
||||||
const DetectTokensController = require('./controllers/detect-tokens')
|
const DetectTokensController = require('./controllers/detect-tokens')
|
||||||
const ConfigManager = require('./lib/config-manager')
|
|
||||||
const nodeify = require('./lib/nodeify')
|
const nodeify = require('./lib/nodeify')
|
||||||
const accountImporter = require('./account-import-strategies')
|
const accountImporter = require('./account-import-strategies')
|
||||||
const getBuyEthUrl = require('./lib/buy-eth-url')
|
const getBuyEthUrl = require('./lib/buy-eth-url')
|
||||||
|
@ -46,9 +45,12 @@ 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')
|
||||||
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
const sigUtil = require('eth-sig-util')
|
||||||
|
|
||||||
module.exports = class MetamaskController extends EventEmitter {
|
module.exports = class MetamaskController extends EventEmitter {
|
||||||
|
|
||||||
|
@ -66,6 +68,10 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
const initState = opts.initState || {}
|
const initState = opts.initState || {}
|
||||||
this.recordFirstTimeInfo(initState)
|
this.recordFirstTimeInfo(initState)
|
||||||
|
|
||||||
|
// this keeps track of how many "controllerStream" connections are open
|
||||||
|
// the only thing that uses controller connections are open metamask UI instances
|
||||||
|
this.activeControllerConnections = 0
|
||||||
|
|
||||||
// platform-specific api
|
// platform-specific api
|
||||||
this.platform = opts.platform
|
this.platform = opts.platform
|
||||||
|
|
||||||
|
@ -78,15 +84,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
// network store
|
// network store
|
||||||
this.networkController = new NetworkController(initState.NetworkController)
|
this.networkController = new NetworkController(initState.NetworkController)
|
||||||
|
|
||||||
// config manager
|
|
||||||
this.configManager = new ConfigManager({
|
|
||||||
store: this.store,
|
|
||||||
})
|
|
||||||
|
|
||||||
// preferences controller
|
// preferences controller
|
||||||
this.preferencesController = new PreferencesController({
|
this.preferencesController = new PreferencesController({
|
||||||
initState: initState.PreferencesController,
|
initState: initState.PreferencesController,
|
||||||
initLangCode: opts.initLangCode,
|
initLangCode: opts.initLangCode,
|
||||||
|
showWatchAssetUi: opts.showWatchAssetUi,
|
||||||
network: this.networkController,
|
network: this.networkController,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -107,8 +109,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({
|
||||||
|
@ -125,9 +128,17 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
provider: this.provider,
|
provider: this.provider,
|
||||||
blockTracker: this.blockTracker,
|
blockTracker: this.blockTracker,
|
||||||
})
|
})
|
||||||
|
// start and stop polling for balances based on activeControllerConnections
|
||||||
|
this.on('controllerConnectionChanged', (activeControllerConnections) => {
|
||||||
|
if (activeControllerConnections > 0) {
|
||||||
|
this.accountTracker.start()
|
||||||
|
} else {
|
||||||
|
this.accountTracker.stop()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 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,
|
||||||
|
@ -135,19 +146,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
encryptor: opts.encryptor || undefined,
|
encryptor: opts.encryptor || undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
// If only one account exists, make sure it is selected.
|
this.keyringController.memStore.subscribe((s) => this._onKeyringControllerUpdate(s))
|
||||||
this.keyringController.memStore.subscribe((state) => {
|
|
||||||
const addresses = state.keyrings.reduce((res, keyring) => {
|
|
||||||
return res.concat(keyring.accounts)
|
|
||||||
}, [])
|
|
||||||
if (addresses.length === 1) {
|
|
||||||
const address = addresses[0]
|
|
||||||
this.preferencesController.setSelectedAddress(address)
|
|
||||||
}
|
|
||||||
// ensure preferences + identities controller know about all addresses
|
|
||||||
this.preferencesController.addAddresses(addresses)
|
|
||||||
this.accountTracker.syncWithAddresses(addresses)
|
|
||||||
})
|
|
||||||
|
|
||||||
// detect tokens controller
|
// detect tokens controller
|
||||||
this.detectTokensController = new DetectTokensController({
|
this.detectTokensController = new DetectTokensController({
|
||||||
|
@ -174,7 +173,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
blockTracker: this.blockTracker,
|
blockTracker: this.blockTracker,
|
||||||
getGasPrice: this.getGasPrice.bind(this),
|
getGasPrice: this.getGasPrice.bind(this),
|
||||||
})
|
})
|
||||||
this.txController.on('newUnapprovedTx', opts.showUnapprovedTx.bind(opts))
|
this.txController.on('newUnapprovedTx', () => opts.showUnapprovedTx())
|
||||||
|
|
||||||
this.txController.on(`tx:status-update`, (txId, status) => {
|
this.txController.on(`tx:status-update`, (txId, status) => {
|
||||||
if (status === 'confirmed' || status === 'failed') {
|
if (status === 'confirmed' || status === 'failed') {
|
||||||
|
@ -208,7 +207,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
this.networkController.lookupNetwork()
|
this.networkController.lookupNetwork()
|
||||||
this.messageManager = new MessageManager()
|
this.messageManager = new MessageManager()
|
||||||
this.personalMessageManager = new PersonalMessageManager()
|
this.personalMessageManager = new PersonalMessageManager()
|
||||||
this.typedMessageManager = new TypedMessageManager()
|
this.typedMessageManager = new TypedMessageManager({ networkController: this.networkController })
|
||||||
this.publicConfigStore = this.initPublicConfigStore()
|
this.publicConfigStore = this.initPublicConfigStore()
|
||||||
|
|
||||||
this.store.updateStructure({
|
this.store.updateStructure({
|
||||||
|
@ -252,30 +251,23 @@ 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),
|
|
||||||
}
|
}
|
||||||
const providerProxy = this.networkController.initializeProvider(providerOpts)
|
const providerProxy = this.networkController.initializeProvider(providerOpts)
|
||||||
return providerProxy
|
return providerProxy
|
||||||
|
@ -317,18 +309,15 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
* @returns {Object} status
|
* @returns {Object} status
|
||||||
*/
|
*/
|
||||||
getState () {
|
getState () {
|
||||||
const wallet = this.configManager.getWallet()
|
|
||||||
const vault = this.keyringController.store.getState().vault
|
const vault = this.keyringController.store.getState().vault
|
||||||
const isInitialized = (!!wallet || !!vault)
|
const isInitialized = !!vault
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...{ isInitialized },
|
...{ isInitialized },
|
||||||
...this.memStore.getFlatState(),
|
...this.memStore.getFlatState(),
|
||||||
...this.configManager.getConfig(),
|
|
||||||
...{
|
...{
|
||||||
lostAccounts: this.configManager.getLostAccounts(),
|
// TODO: Remove usages of lost accounts
|
||||||
seedWords: this.configManager.getSeedWords(),
|
lostAccounts: [],
|
||||||
forgottenPassword: this.configManager.getPasswordForgotten(),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -377,9 +366,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),
|
||||||
|
@ -392,6 +379,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController),
|
setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController),
|
||||||
addToken: nodeify(preferencesController.addToken, preferencesController),
|
addToken: nodeify(preferencesController.addToken, preferencesController),
|
||||||
removeToken: nodeify(preferencesController.removeToken, preferencesController),
|
removeToken: nodeify(preferencesController.removeToken, preferencesController),
|
||||||
|
removeSuggestedTokens: nodeify(preferencesController.removeSuggestedTokens, preferencesController),
|
||||||
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
|
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
|
||||||
setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController),
|
setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController),
|
||||||
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
|
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
|
||||||
|
@ -411,6 +399,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
updateTransaction: nodeify(txController.updateTransaction, txController),
|
updateTransaction: nodeify(txController.updateTransaction, txController),
|
||||||
updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
|
updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
|
||||||
retryTransaction: nodeify(this.retryTransaction, this),
|
retryTransaction: nodeify(this.retryTransaction, this),
|
||||||
|
createCancelTransaction: nodeify(this.createCancelTransaction, this),
|
||||||
getFilteredTxList: nodeify(txController.getFilteredTxList, txController),
|
getFilteredTxList: nodeify(txController.getFilteredTxList, txController),
|
||||||
isNonceTaken: nodeify(txController.isNonceTaken, txController),
|
isNonceTaken: nodeify(txController.isNonceTaken, txController),
|
||||||
estimateGas: nodeify(this.estimateGas, this),
|
estimateGas: nodeify(this.estimateGas, this),
|
||||||
|
@ -481,12 +470,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 +506,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,25 +573,40 @@ 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) {
|
|
||||||
case 'trezor':
|
|
||||||
const keyringController = this.keyringController
|
|
||||||
const oldAccounts = await keyringController.getAccounts()
|
|
||||||
let keyring = await keyringController.getKeyringsByType(
|
|
||||||
'Trezor Hardware'
|
|
||||||
)[0]
|
|
||||||
if (!keyring) {
|
|
||||||
keyring = await this.keyringController.addNewKeyring('Trezor Hardware')
|
|
||||||
}
|
|
||||||
let accounts = []
|
let accounts = []
|
||||||
|
|
||||||
switch (page) {
|
switch (page) {
|
||||||
case -1:
|
case -1:
|
||||||
accounts = await keyring.getPreviousPage()
|
accounts = await keyring.getPreviousPage()
|
||||||
|
@ -572,13 +620,10 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
|
|
||||||
// Merge with existing accounts
|
// Merge with existing accounts
|
||||||
// and make sure addresses are not repeated
|
// 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())))]
|
const accountsToTrack = [...new Set(oldAccounts.concat(accounts.map(a => a.address.toLowerCase())))]
|
||||||
this.accountTracker.syncWithAddresses(accountsToTrack)
|
this.accountTracker.syncWithAddresses(accountsToTrack)
|
||||||
return accounts
|
return accounts
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error('MetamaskController:connectHardware - Unknown device')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -586,21 +631,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) {
|
|
||||||
case 'trezor':
|
|
||||||
const keyringController = this.keyringController
|
|
||||||
const keyring = await keyringController.getKeyringsByType(
|
|
||||||
'Trezor Hardware'
|
|
||||||
)[0]
|
|
||||||
if (!keyring) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return keyring.isUnlocked()
|
return keyring.isUnlocked()
|
||||||
default:
|
|
||||||
throw new Error('MetamaskController:checkHardwareStatus - Unknown device')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -610,20 +643,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
async forgetDevice (deviceName) {
|
async forgetDevice (deviceName) {
|
||||||
|
|
||||||
switch (deviceName) {
|
const keyring = await this.getKeyringForDevice(deviceName)
|
||||||
case 'trezor':
|
|
||||||
const keyringController = this.keyringController
|
|
||||||
const keyring = await keyringController.getKeyringsByType(
|
|
||||||
'Trezor Hardware'
|
|
||||||
)[0]
|
|
||||||
if (!keyring) {
|
|
||||||
throw new Error('MetamaskController:forgetDevice - Trezor Hardware keyring not found')
|
|
||||||
}
|
|
||||||
keyring.forgetDevice()
|
keyring.forgetDevice()
|
||||||
return true
|
return true
|
||||||
default:
|
|
||||||
throw new Error('MetamaskController:forgetDevice - Unknown device')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -631,23 +653,19 @@ 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}`)
|
// Set the account label to Trezor 1 / Ledger 1, etc
|
||||||
|
this.preferencesController.setAccountLabel(address, `${deviceName[0].toUpperCase()}${deviceName.slice(1)} ${parseInt(index, 10) + 1}`)
|
||||||
|
// Select the account
|
||||||
this.preferencesController.setSelectedAddress(address)
|
this.preferencesController.setSelectedAddress(address)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -701,7 +719,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
|
|
||||||
this.verifySeedPhrase()
|
this.verifySeedPhrase()
|
||||||
.then((seedWords) => {
|
.then((seedWords) => {
|
||||||
this.configManager.setSeedWords(seedWords)
|
this.preferencesController.setSeedWords(seedWords)
|
||||||
return cb(null, seedWords)
|
return cb(null, seedWords)
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
@ -750,7 +768,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
* @param {function} cb Callback function called with the current address.
|
* @param {function} cb Callback function called with the current address.
|
||||||
*/
|
*/
|
||||||
clearSeedWordCache (cb) {
|
clearSeedWordCache (cb) {
|
||||||
this.configManager.setSeedWords(null)
|
this.preferencesController.setSeedWords(null)
|
||||||
cb(null, this.preferencesController.getSelectedAddress())
|
cb(null, this.preferencesController.getSelectedAddress())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -779,7 +797,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
// Remove account from the preferences controller
|
// Remove account from the preferences controller
|
||||||
this.preferencesController.removeAddress(address)
|
this.preferencesController.removeAddress(address)
|
||||||
// Remove account from the account tracker controller
|
// Remove account from the account tracker controller
|
||||||
this.accountTracker.removeAccount(address)
|
this.accountTracker.removeAccount([address])
|
||||||
|
|
||||||
// Remove account from the keyring
|
// Remove account from the keyring
|
||||||
await this.keyringController.removeAccount(address)
|
await this.keyringController.removeAccount(address)
|
||||||
return address
|
return address
|
||||||
|
@ -809,6 +828,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 +851,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 +909,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 +962,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 {
|
|
||||||
msgId = this.typedMessageManager.addUnapprovedMessage(msgParams)
|
|
||||||
this.sendUpdate()
|
this.sendUpdate()
|
||||||
this.opts.showUnconfirmedMessage()
|
this.opts.showUnconfirmedMessage()
|
||||||
} catch (e) {
|
return promise
|
||||||
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)}`)))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -982,22 +976,31 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
* @param {Object} msgParams - The params passed to eth_signTypedData.
|
* @param {Object} msgParams - The params passed to eth_signTypedData.
|
||||||
* @returns {Object} Full state update.
|
* @returns {Object} Full state update.
|
||||||
*/
|
*/
|
||||||
signTypedMessage (msgParams) {
|
async signTypedMessage (msgParams) {
|
||||||
log.info('MetaMaskController - signTypedMessage')
|
log.info('MetaMaskController - eth_signTypedData')
|
||||||
const msgId = msgParams.metamaskId
|
const msgId = msgParams.metamaskId
|
||||||
// sets the status op the message to 'approved'
|
const version = msgParams.version
|
||||||
// and removes the metamaskId for signing
|
try {
|
||||||
return this.typedMessageManager.approveMessage(msgParams)
|
const cleanMsgParams = await this.typedMessageManager.approveMessage(msgParams)
|
||||||
.then((cleanMsgParams) => {
|
const address = sigUtil.normalize(cleanMsgParams.from)
|
||||||
// signs the message
|
const keyring = await this.keyringController.getKeyringForAccount(address)
|
||||||
return this.keyringController.signTypedMessage(cleanMsgParams)
|
const wallet = keyring._getWalletForAccount(address)
|
||||||
})
|
const privKey = ethUtil.toBuffer(wallet.getPrivateKey())
|
||||||
.then((rawSig) => {
|
let signature
|
||||||
// tells the listener that the message has been signed
|
switch (version) {
|
||||||
// and can be returned to the dapp
|
case 'V1':
|
||||||
this.typedMessageManager.setMsgStatusSigned(msgId, rawSig)
|
signature = sigUtil.signTypedDataLegacy(privKey, { data: cleanMsgParams.data })
|
||||||
|
break
|
||||||
|
case 'V3':
|
||||||
|
signature = sigUtil.signTypedData(privKey, { data: JSON.parse(cleanMsgParams.data) })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
this.typedMessageManager.setMsgStatusSigned(msgId, signature)
|
||||||
return this.getState()
|
return this.getState()
|
||||||
})
|
} catch (error) {
|
||||||
|
log.info('MetaMaskController - eth_signTypedData failed.', error)
|
||||||
|
this.typedMessageManager.errorMessage(msgId, error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1035,35 +1038,16 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
* A legacy method used to record user confirmation that they understand
|
* A legacy method used to record user confirmation that they understand
|
||||||
* that some of their accounts have been recovered but should be backed up.
|
* that some of their accounts have been recovered but should be backed up.
|
||||||
|
* This function no longer does anything and will be removed.
|
||||||
*
|
*
|
||||||
* @deprecated
|
* @deprecated
|
||||||
* @param {Function} cb - A callback function called with a full state update.
|
* @param {Function} cb - A callback function called with a full state update.
|
||||||
*/
|
*/
|
||||||
markAccountsFound (cb) {
|
markAccountsFound (cb) {
|
||||||
this.configManager.setLostAccounts([])
|
// TODO Remove me
|
||||||
this.sendUpdate()
|
|
||||||
cb(null, this.getState())
|
cb(null, this.getState())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A legacy method (probably dead code) that was used when we swapped out our
|
|
||||||
* key management library that we depended on.
|
|
||||||
*
|
|
||||||
* Described in:
|
|
||||||
* https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd
|
|
||||||
*
|
|
||||||
* @deprecated
|
|
||||||
* @param {} migratorOutput
|
|
||||||
*/
|
|
||||||
restoreOldLostAccounts (migratorOutput) {
|
|
||||||
const { lostAccounts } = migratorOutput
|
|
||||||
if (lostAccounts) {
|
|
||||||
this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address))
|
|
||||||
return this.importLostAccounts(migratorOutput)
|
|
||||||
}
|
|
||||||
return Promise.resolve(migratorOutput)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An account object
|
* An account object
|
||||||
* @typedef Account
|
* @typedef Account
|
||||||
|
@ -1108,6 +1092,19 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows a user to attempt to cancel a previously submitted transaction by creating a new
|
||||||
|
* transaction.
|
||||||
|
* @param {number} originalTxId - the id of the txMeta that you want to attempt to cancel
|
||||||
|
* @param {string=} customGasPrice - the hex value to use for the cancel transaction
|
||||||
|
* @returns {object} MetaMask state
|
||||||
|
*/
|
||||||
|
async createCancelTransaction (originalTxId, customGasPrice, cb) {
|
||||||
|
await this.txController.createCancelTransaction(originalTxId, customGasPrice)
|
||||||
|
const state = await this.getState()
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
estimateGas (estimateGasParams) {
|
estimateGas (estimateGasParams) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
return this.txController.txGasUtil.query.estimateGas(estimateGasParams, (err, res) => {
|
return this.txController.txGasUtil.query.estimateGas(estimateGasParams, (err, res) => {
|
||||||
|
@ -1129,7 +1126,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
* @param {Function} cb - A callback function called when complete.
|
* @param {Function} cb - A callback function called when complete.
|
||||||
*/
|
*/
|
||||||
markPasswordForgotten (cb) {
|
markPasswordForgotten (cb) {
|
||||||
this.configManager.setPasswordForgotten(true)
|
this.preferencesController.setPasswordForgotten(true)
|
||||||
this.sendUpdate()
|
this.sendUpdate()
|
||||||
cb()
|
cb()
|
||||||
}
|
}
|
||||||
|
@ -1139,7 +1136,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
* @param {Function} cb - A callback function called when complete.
|
* @param {Function} cb - A callback function called when complete.
|
||||||
*/
|
*/
|
||||||
unMarkPasswordForgotten (cb) {
|
unMarkPasswordForgotten (cb) {
|
||||||
this.configManager.setPasswordForgotten(false)
|
this.preferencesController.setPasswordForgotten(false)
|
||||||
this.sendUpdate()
|
this.sendUpdate()
|
||||||
cb()
|
cb()
|
||||||
}
|
}
|
||||||
|
@ -1210,18 +1207,28 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
setupControllerConnection (outStream) {
|
setupControllerConnection (outStream) {
|
||||||
const api = this.getApi()
|
const api = this.getApi()
|
||||||
const dnode = Dnode(api)
|
const dnode = Dnode(api)
|
||||||
|
// report new active controller connection
|
||||||
|
this.activeControllerConnections++
|
||||||
|
this.emit('controllerConnectionChanged', this.activeControllerConnections)
|
||||||
|
// connect dnode api to remote connection
|
||||||
pump(
|
pump(
|
||||||
outStream,
|
outStream,
|
||||||
dnode,
|
dnode,
|
||||||
outStream,
|
outStream,
|
||||||
(err) => {
|
(err) => {
|
||||||
|
// report new active controller connection
|
||||||
|
this.activeControllerConnections--
|
||||||
|
this.emit('controllerConnectionChanged', this.activeControllerConnections)
|
||||||
|
// report any error
|
||||||
if (err) log.error(err)
|
if (err) log.error(err)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
dnode.on('remote', (remote) => {
|
dnode.on('remote', (remote) => {
|
||||||
// push updates to popup
|
// push updates to popup
|
||||||
const sendUpdate = remote.sendUpdate.bind(remote)
|
const sendUpdate = (update) => remote.sendUpdate(update)
|
||||||
this.on('update', sendUpdate)
|
this.on('update', sendUpdate)
|
||||||
|
// remove update listener once the connection ends
|
||||||
|
dnode.on('end', () => this.removeListener('update', sendUpdate))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1237,12 +1244,16 @@ 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 }))
|
||||||
engine.push(createLoggerMiddleware({ origin }))
|
engine.push(createLoggerMiddleware({ origin }))
|
||||||
engine.push(filterMiddleware)
|
engine.push(filterMiddleware)
|
||||||
|
engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController))
|
||||||
|
engine.push(this.createTypedDataMiddleware('eth_signTypedData', 'V1').bind(this))
|
||||||
|
engine.push(this.createTypedDataMiddleware('eth_signTypedData_v1', 'V1').bind(this))
|
||||||
|
engine.push(this.createTypedDataMiddleware('eth_signTypedData_v3', 'V3').bind(this))
|
||||||
engine.push(createProviderMiddleware({ provider: this.provider }))
|
engine.push(createProviderMiddleware({ provider: this.provider }))
|
||||||
|
|
||||||
// setup connection
|
// setup connection
|
||||||
|
@ -1270,15 +1281,45 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
* @param {*} outStream - The stream to provide public config over.
|
* @param {*} outStream - The stream to provide public config over.
|
||||||
*/
|
*/
|
||||||
setupPublicConfig (outStream) {
|
setupPublicConfig (outStream) {
|
||||||
|
const configStream = asStream(this.publicConfigStore)
|
||||||
pump(
|
pump(
|
||||||
asStream(this.publicConfigStore),
|
configStream,
|
||||||
outStream,
|
outStream,
|
||||||
(err) => {
|
(err) => {
|
||||||
|
configStream.destroy()
|
||||||
if (err) log.error(err)
|
if (err) log.error(err)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a KeyringController update
|
||||||
|
* @param {object} state the KC state
|
||||||
|
* @return {Promise<void>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _onKeyringControllerUpdate (state) {
|
||||||
|
const {isUnlocked, keyrings} = state
|
||||||
|
const addresses = keyrings.reduce((acc, {accounts}) => acc.concat(accounts), [])
|
||||||
|
|
||||||
|
if (!addresses.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure preferences + identities controller know about all addresses
|
||||||
|
this.preferencesController.addAddresses(addresses)
|
||||||
|
this.accountTracker.syncWithAddresses(addresses)
|
||||||
|
|
||||||
|
const wasLocked = !isUnlocked
|
||||||
|
if (wasLocked) {
|
||||||
|
const oldSelectedAddress = this.preferencesController.getSelectedAddress()
|
||||||
|
if (!addresses.includes(oldSelectedAddress)) {
|
||||||
|
const address = addresses[0]
|
||||||
|
await this.preferencesController.setSelectedAddress(address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A method for emitting the full MetaMask state to all registered listeners.
|
* A method for emitting the full MetaMask state to all registered listeners.
|
||||||
* @private
|
* @private
|
||||||
|
@ -1425,6 +1466,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Replace isClientOpen methods with `controllerConnectionChanged` events.
|
||||||
/**
|
/**
|
||||||
* A method for recording whether the MetaMask user interface is open or not.
|
* A method for recording whether the MetaMask user interface is open or not.
|
||||||
* @private
|
* @private
|
||||||
|
@ -1445,4 +1487,34 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
set isClientOpenAndUnlocked (active) {
|
set isClientOpenAndUnlocked (active) {
|
||||||
this.tokenRatesController.isActive = active
|
this.tokenRatesController.isActive = active
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates RPC engine middleware for processing eth_signTypedData requests
|
||||||
|
*
|
||||||
|
* @param {Object} req - request object
|
||||||
|
* @param {Object} res - response object
|
||||||
|
* @param {Function} - next
|
||||||
|
* @param {Function} - end
|
||||||
|
*/
|
||||||
|
createTypedDataMiddleware (methodName, version) {
|
||||||
|
return async (req, res, next, end) => {
|
||||||
|
const { method, params } = req
|
||||||
|
if (method === methodName) {
|
||||||
|
const promise = this.typedMessageManager.addUnapprovedMessageAsync({
|
||||||
|
data: params.length >= 1 && params[0],
|
||||||
|
from: params.length >= 2 && params[1],
|
||||||
|
}, req, version)
|
||||||
|
this.sendUpdate()
|
||||||
|
this.opts.showUnconfirmedMessage()
|
||||||
|
try {
|
||||||
|
res.result = await promise
|
||||||
|
end()
|
||||||
|
} catch (error) {
|
||||||
|
end(error)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
const version = 5
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
This is an incomplete migration bc it requires post-decrypted data
|
|
||||||
which we dont have access to at the time of this writing.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const ObservableStore = require('obs-store')
|
|
||||||
const ConfigManager = require('../../app/scripts/lib/config-manager')
|
|
||||||
const IdentityStoreMigrator = require('../../app/scripts/lib/idStore-migrator')
|
|
||||||
const KeyringController = require('eth-keyring-controller')
|
|
||||||
|
|
||||||
const password = 'obviously not correct'
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
version,
|
|
||||||
|
|
||||||
migrate: function (versionedData) {
|
|
||||||
versionedData.meta.version = version
|
|
||||||
|
|
||||||
const store = new ObservableStore(versionedData.data)
|
|
||||||
const configManager = new ConfigManager({ store })
|
|
||||||
const idStoreMigrator = new IdentityStoreMigrator({ configManager })
|
|
||||||
const keyringController = new KeyringController({
|
|
||||||
configManager: configManager,
|
|
||||||
})
|
|
||||||
|
|
||||||
// attempt to migrate to multiVault
|
|
||||||
return idStoreMigrator.migratedVaultForPassword(password)
|
|
||||||
.then((result) => {
|
|
||||||
// skip if nothing to migrate
|
|
||||||
if (!result) return Promise.resolve(versionedData)
|
|
||||||
delete versionedData.data.wallet
|
|
||||||
// create new keyrings
|
|
||||||
const privKeys = result.lostAccounts.map(acct => acct.privateKey)
|
|
||||||
return Promise.all([
|
|
||||||
keyringController.restoreKeyring(result.serialized),
|
|
||||||
keyringController.restoreKeyring({ type: 'Simple Key Pair', data: privKeys }),
|
|
||||||
]).then(() => {
|
|
||||||
return keyringController.persistAllKeyrings(password)
|
|
||||||
}).then(() => {
|
|
||||||
// copy result on to state object
|
|
||||||
versionedData.data = store.get()
|
|
||||||
return Promise.resolve(versionedData)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -7,7 +7,7 @@ const uniqBy = require('lodash.uniqby')
|
||||||
|
|
||||||
module.exports = class NoticeController extends EventEmitter {
|
module.exports = class NoticeController extends EventEmitter {
|
||||||
|
|
||||||
constructor (opts) {
|
constructor (opts = {}) {
|
||||||
super()
|
super()
|
||||||
this.noticePoller = null
|
this.noticePoller = null
|
||||||
this.firstVersion = opts.firstVersion
|
this.firstVersion = opts.firstVersion
|
||||||
|
|
|
@ -2,7 +2,7 @@ const injectCss = require('inject-css')
|
||||||
const OldMetaMaskUiCss = require('../../old-ui/css')
|
const OldMetaMaskUiCss = require('../../old-ui/css')
|
||||||
const NewMetaMaskUiCss = require('../../ui/css')
|
const NewMetaMaskUiCss = require('../../ui/css')
|
||||||
const startPopup = require('./popup-core')
|
const startPopup = require('./popup-core')
|
||||||
const PortStream = require('./lib/port-stream.js')
|
const PortStream = require('extension-port-stream')
|
||||||
const { getEnvironmentType } = require('./lib/util')
|
const { getEnvironmentType } = require('./lib/util')
|
||||||
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('./lib/enums')
|
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('./lib/enums')
|
||||||
const extension = require('extensionizer')
|
const extension = require('extensionizer')
|
||||||
|
|
|
@ -123,6 +123,7 @@
|
||||||
"modalState": {},
|
"modalState": {},
|
||||||
"previousModalState": {}
|
"previousModalState": {}
|
||||||
},
|
},
|
||||||
|
"sidebar": {},
|
||||||
"transForward": true,
|
"transForward": true,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"warning": null,
|
"warning": null,
|
||||||
|
|
|
@ -141,6 +141,7 @@
|
||||||
"accountDetail": {
|
"accountDetail": {
|
||||||
"subview": "transactions"
|
"subview": "transactions"
|
||||||
},
|
},
|
||||||
|
"sidebar": {},
|
||||||
"modal": {
|
"modal": {
|
||||||
"modalState": {},
|
"modalState": {},
|
||||||
"previousModalState": {}
|
"previousModalState": {}
|
||||||
|
|
|
@ -162,6 +162,7 @@
|
||||||
"accountDetail": {
|
"accountDetail": {
|
||||||
"subview": "transactions"
|
"subview": "transactions"
|
||||||
},
|
},
|
||||||
|
"sidebar": {},
|
||||||
"modal": {
|
"modal": {
|
||||||
"modalState": {},
|
"modalState": {},
|
||||||
"previousModalState": {}
|
"previousModalState": {}
|
||||||
|
|
|
@ -120,6 +120,7 @@
|
||||||
"accountDetail": {
|
"accountDetail": {
|
||||||
"subview": "transactions"
|
"subview": "transactions"
|
||||||
},
|
},
|
||||||
|
"sidebar": {},
|
||||||
"modal": {
|
"modal": {
|
||||||
"modalState": {},
|
"modalState": {},
|
||||||
"previousModalState": {}
|
"previousModalState": {}
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
"accountDetail": {
|
"accountDetail": {
|
||||||
"subview": "transactions"
|
"subview": "transactions"
|
||||||
},
|
},
|
||||||
|
"sidebar": {},
|
||||||
"transForward": true,
|
"transForward": true,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"warning": null,
|
"warning": null,
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
"name": "Send Account 4"
|
"name": "Send Account 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"assetImages": {},
|
||||||
"unapprovedTxs": {},
|
"unapprovedTxs": {},
|
||||||
"currentCurrency": "USD",
|
"currentCurrency": "USD",
|
||||||
"conversionRate": 1200.88200327,
|
"conversionRate": 1200.88200327,
|
||||||
|
@ -141,6 +142,7 @@
|
||||||
"accountDetail": {
|
"accountDetail": {
|
||||||
"subview": "transactions"
|
"subview": "transactions"
|
||||||
},
|
},
|
||||||
|
"sidebar": {},
|
||||||
"modal": {
|
"modal": {
|
||||||
"modalState": {},
|
"modalState": {},
|
||||||
"previousModalState": {}
|
"previousModalState": {}
|
||||||
|
|
|
@ -61,6 +61,7 @@
|
||||||
"name": "Address Book Account 1"
|
"name": "Address Book Account 1"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"assetImages": {},
|
||||||
"tokens": [],
|
"tokens": [],
|
||||||
"transactions": {},
|
"transactions": {},
|
||||||
"selectedAddressTxList": [],
|
"selectedAddressTxList": [],
|
||||||
|
@ -120,6 +121,7 @@
|
||||||
"accountDetail": {
|
"accountDetail": {
|
||||||
"subview": "transactions"
|
"subview": "transactions"
|
||||||
},
|
},
|
||||||
|
"sidebar": {},
|
||||||
"modal": {
|
"modal": {
|
||||||
"modalState": {},
|
"modalState": {},
|
||||||
"previousModalState": {}
|
"previousModalState": {}
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
"name": "Account 4"
|
"name": "Account 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"assetImages": {},
|
||||||
"unapprovedTxs": {},
|
"unapprovedTxs": {},
|
||||||
"currentCurrency": "USD",
|
"currentCurrency": "USD",
|
||||||
"conversionRate": 16.88200327,
|
"conversionRate": 16.88200327,
|
||||||
|
@ -99,6 +100,7 @@
|
||||||
"accountExport": "none",
|
"accountExport": "none",
|
||||||
"privateKey": ""
|
"privateKey": ""
|
||||||
},
|
},
|
||||||
|
"sidebar": {},
|
||||||
"transForward": true,
|
"transForward": true,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"warning": null,
|
"warning": null,
|
||||||
|
|
|
@ -118,6 +118,7 @@
|
||||||
"modalState": {},
|
"modalState": {},
|
||||||
"previousModalState": {}
|
"previousModalState": {}
|
||||||
},
|
},
|
||||||
|
"sidebar": {},
|
||||||
"transForward": true,
|
"transForward": true,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"warning": null,
|
"warning": null,
|
||||||
|
|
|
@ -5,7 +5,6 @@ To add another network to our dropdown menu, make sure the following files are a
|
||||||
```
|
```
|
||||||
app/scripts/config.js
|
app/scripts/config.js
|
||||||
app/scripts/lib/buy-eth-url.js
|
app/scripts/lib/buy-eth-url.js
|
||||||
app/scripts/lib/config-manager.js
|
|
||||||
ui/app/app.js
|
ui/app/app.js
|
||||||
ui/app/components/buy-button-subview.js
|
ui/app/components/buy-button-subview.js
|
||||||
ui/app/components/drop-menu-item.js
|
ui/app/components/drop-menu-item.js
|
||||||
|
|
|
@ -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!
|
||||||
|
|
|
@ -63,7 +63,9 @@ class CreatePasswordScreen extends Component {
|
||||||
return password === confirmPassword
|
return password === confirmPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
createAccount = () => {
|
createAccount = (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
if (!this.isValid()) {
|
if (!this.isValid()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -127,7 +129,7 @@ class CreatePasswordScreen extends Component {
|
||||||
It allows you to hold ether & tokens, and interact with decentralized applications.
|
It allows you to hold ether & tokens, and interact with decentralized applications.
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
</div>}
|
||||||
<div className="create-password">
|
<form className="create-password">
|
||||||
<div className="create-password__title">
|
<div className="create-password__title">
|
||||||
Create Password
|
Create Password
|
||||||
</div>
|
</div>
|
||||||
|
@ -188,7 +190,7 @@ class CreatePasswordScreen extends Component {
|
||||||
</a>
|
</a>
|
||||||
{ */ }
|
{ */ }
|
||||||
<Breadcrumbs total={3} currentIndex={0} />
|
<Breadcrumbs total={3} currentIndex={0} />
|
||||||
</div>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -32,6 +32,7 @@ function mapStateToProps (state) {
|
||||||
currentCurrency: state.metamask.currentCurrency,
|
currentCurrency: state.metamask.currentCurrency,
|
||||||
currentAccountTab: state.metamask.currentAccountTab,
|
currentAccountTab: state.metamask.currentAccountTab,
|
||||||
tokens: state.metamask.tokens,
|
tokens: state.metamask.tokens,
|
||||||
|
suggestedTokens: state.metamask.suggestedTokens,
|
||||||
computedBalances: state.metamask.computedBalances,
|
computedBalances: state.metamask.computedBalances,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +50,10 @@ AccountDetailScreen.prototype.render = function () {
|
||||||
var account = props.accounts[selected]
|
var account = props.accounts[selected]
|
||||||
const { network, conversionRate, currentCurrency } = props
|
const { network, conversionRate, currentCurrency } = props
|
||||||
|
|
||||||
|
if (Object.keys(props.suggestedTokens).length > 0) {
|
||||||
|
this.props.dispatch(actions.showAddSuggestedTokenPage())
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
h('.account-detail-section.full-flex-height', [
|
h('.account-detail-section.full-flex-height', [
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
const inherits = require('util').inherits
|
||||||
|
const Component = require('react').Component
|
||||||
|
const h = require('react-hyperscript')
|
||||||
|
const connect = require('react-redux').connect
|
||||||
|
const actions = require('../../ui/app/actions')
|
||||||
|
const Tooltip = require('./components/tooltip.js')
|
||||||
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
const Copyable = require('./components/copyable')
|
||||||
|
const addressSummary = require('./util').addressSummary
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = connect(mapStateToProps)(AddSuggestedTokenScreen)
|
||||||
|
|
||||||
|
function mapStateToProps (state) {
|
||||||
|
return {
|
||||||
|
identities: state.metamask.identities,
|
||||||
|
suggestedTokens: state.metamask.suggestedTokens,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inherits(AddSuggestedTokenScreen, Component)
|
||||||
|
function AddSuggestedTokenScreen () {
|
||||||
|
this.state = {
|
||||||
|
warning: null,
|
||||||
|
}
|
||||||
|
Component.call(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
AddSuggestedTokenScreen.prototype.render = function () {
|
||||||
|
const state = this.state
|
||||||
|
const props = this.props
|
||||||
|
const { warning } = state
|
||||||
|
const key = Object.keys(props.suggestedTokens)[0]
|
||||||
|
const { address, symbol, decimals } = props.suggestedTokens[key]
|
||||||
|
|
||||||
|
return (
|
||||||
|
h('.flex-column.flex-grow', [
|
||||||
|
|
||||||
|
// subtitle and nav
|
||||||
|
h('.section-title.flex-row.flex-center', [
|
||||||
|
h('h2.page-subtitle', 'Add Suggested Token'),
|
||||||
|
]),
|
||||||
|
|
||||||
|
h('.error', {
|
||||||
|
style: {
|
||||||
|
display: warning ? 'block' : 'none',
|
||||||
|
padding: '0 20px',
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
}, warning),
|
||||||
|
|
||||||
|
// conf view
|
||||||
|
h('.flex-column.flex-justify-center.flex-grow.select-none', [
|
||||||
|
h('.flex-space-around', {
|
||||||
|
style: {
|
||||||
|
padding: '20px',
|
||||||
|
},
|
||||||
|
}, [
|
||||||
|
|
||||||
|
h('div', [
|
||||||
|
h(Tooltip, {
|
||||||
|
position: 'top',
|
||||||
|
title: 'The contract of the actual token contract. Click for more info.',
|
||||||
|
}, [
|
||||||
|
h('a', {
|
||||||
|
style: { fontWeight: 'bold', paddingRight: '10px'},
|
||||||
|
href: 'https://support.metamask.io/kb/article/24-what-is-a-token-contract-address',
|
||||||
|
target: '_blank',
|
||||||
|
}, [
|
||||||
|
h('span', 'Token Contract Address '),
|
||||||
|
h('i.fa.fa-question-circle'),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
|
||||||
|
h('div', {
|
||||||
|
style: { display: 'flex' },
|
||||||
|
}, [
|
||||||
|
h(Copyable, {
|
||||||
|
value: ethUtil.toChecksumAddress(address),
|
||||||
|
}, [
|
||||||
|
h('span#token-address', {
|
||||||
|
style: {
|
||||||
|
width: 'inherit',
|
||||||
|
flex: '1 0 auto',
|
||||||
|
height: '30px',
|
||||||
|
margin: '8px',
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
}, addressSummary(address, 24, 4, false)),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
|
||||||
|
h('div', [
|
||||||
|
h('span', {
|
||||||
|
style: { fontWeight: 'bold', paddingRight: '10px'},
|
||||||
|
}, 'Token Symbol'),
|
||||||
|
]),
|
||||||
|
|
||||||
|
h('div', { style: {display: 'flex'} }, [
|
||||||
|
h('p#token_symbol', {
|
||||||
|
style: {
|
||||||
|
width: 'inherit',
|
||||||
|
flex: '1 0 auto',
|
||||||
|
height: '30px',
|
||||||
|
margin: '8px',
|
||||||
|
},
|
||||||
|
}, symbol),
|
||||||
|
]),
|
||||||
|
|
||||||
|
h('div', [
|
||||||
|
h('span', {
|
||||||
|
style: { fontWeight: 'bold', paddingRight: '10px'},
|
||||||
|
}, 'Decimals of Precision'),
|
||||||
|
]),
|
||||||
|
|
||||||
|
h('div', { style: {display: 'flex'} }, [
|
||||||
|
h('p#token_decimals', {
|
||||||
|
type: 'number',
|
||||||
|
style: {
|
||||||
|
width: 'inherit',
|
||||||
|
flex: '1 0 auto',
|
||||||
|
height: '30px',
|
||||||
|
margin: '8px',
|
||||||
|
},
|
||||||
|
}, decimals),
|
||||||
|
]),
|
||||||
|
|
||||||
|
h('button', {
|
||||||
|
style: {
|
||||||
|
alignSelf: 'center',
|
||||||
|
margin: '8px',
|
||||||
|
},
|
||||||
|
onClick: (event) => {
|
||||||
|
this.props.dispatch(actions.removeSuggestedTokens())
|
||||||
|
},
|
||||||
|
}, 'Cancel'),
|
||||||
|
|
||||||
|
h('button', {
|
||||||
|
style: {
|
||||||
|
alignSelf: 'center',
|
||||||
|
margin: '8px',
|
||||||
|
},
|
||||||
|
onClick: (event) => {
|
||||||
|
const valid = this.validateInputs({ address, symbol, decimals })
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
this.props.dispatch(actions.addToken(address.trim(), symbol.trim(), decimals))
|
||||||
|
.then(() => {
|
||||||
|
this.props.dispatch(actions.removeSuggestedTokens())
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}, 'Add'),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AddSuggestedTokenScreen.prototype.componentWillMount = function () {
|
||||||
|
if (typeof global.ethereumProvider === 'undefined') return
|
||||||
|
}
|
||||||
|
|
||||||
|
AddSuggestedTokenScreen.prototype.validateInputs = function (opts) {
|
||||||
|
let msg = ''
|
||||||
|
const identitiesList = Object.keys(this.props.identities)
|
||||||
|
const { address, symbol, decimals } = opts
|
||||||
|
const standardAddress = ethUtil.addHexPrefix(address).toLowerCase()
|
||||||
|
|
||||||
|
const validAddress = ethUtil.isValidAddress(address)
|
||||||
|
if (!validAddress) {
|
||||||
|
msg += 'Address is invalid.'
|
||||||
|
}
|
||||||
|
|
||||||
|
const validDecimals = decimals >= 0 && decimals <= 36
|
||||||
|
if (!validDecimals) {
|
||||||
|
msg += 'Decimals must be at least 0, and not over 36. '
|
||||||
|
}
|
||||||
|
|
||||||
|
const symbolLen = symbol.trim().length
|
||||||
|
const validSymbol = symbolLen > 0 && symbolLen < 10
|
||||||
|
if (!validSymbol) {
|
||||||
|
msg += 'Symbol must be between 0 and 10 characters.'
|
||||||
|
}
|
||||||
|
|
||||||
|
const ownAddress = identitiesList.includes(standardAddress)
|
||||||
|
if (ownAddress) {
|
||||||
|
msg = 'Personal address detected. Input the token contract address.'
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValid = validAddress && validDecimals && !ownAddress
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
this.setState({
|
||||||
|
warning: msg,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.setState({ warning: null })
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid
|
||||||
|
}
|
|
@ -196,7 +196,7 @@ AddTokenScreen.prototype.validateInputs = function () {
|
||||||
msg += 'Address is invalid.'
|
msg += 'Address is invalid.'
|
||||||
}
|
}
|
||||||
|
|
||||||
const validDecimals = decimals >= 0 && decimals < 36
|
const validDecimals = decimals >= 0 && decimals <= 36
|
||||||
if (!validDecimals) {
|
if (!validDecimals) {
|
||||||
msg += 'Decimals must be at least 0, and not over 36. '
|
msg += 'Decimals must be at least 0, and not over 36. '
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
|
||||||
// other views
|
// other views
|
||||||
const ConfigScreen = require('./config')
|
const ConfigScreen = require('./config')
|
||||||
const AddTokenScreen = require('./add-token')
|
const AddTokenScreen = require('./add-token')
|
||||||
|
const AddSuggestedTokenScreen = require('./add-suggested-token')
|
||||||
const Import = require('./accounts/import')
|
const Import = require('./accounts/import')
|
||||||
const InfoScreen = require('./info')
|
const InfoScreen = require('./info')
|
||||||
const NewUiAnnouncement = require('./new-ui-annoucement')
|
const NewUiAnnouncement = require('./new-ui-annoucement')
|
||||||
|
@ -74,6 +75,7 @@ function mapStateToProps (state) {
|
||||||
lostAccounts: state.metamask.lostAccounts,
|
lostAccounts: state.metamask.lostAccounts,
|
||||||
frequentRpcList: state.metamask.frequentRpcList || [],
|
frequentRpcList: state.metamask.frequentRpcList || [],
|
||||||
featureFlags,
|
featureFlags,
|
||||||
|
suggestedTokens: state.metamask.suggestedTokens,
|
||||||
|
|
||||||
// state needed to get account dropdown temporarily rendering from app bar
|
// state needed to get account dropdown temporarily rendering from app bar
|
||||||
identities,
|
identities,
|
||||||
|
@ -236,6 +238,10 @@ App.prototype.renderPrimary = function () {
|
||||||
log.debug('rendering add-token screen from unlock screen.')
|
log.debug('rendering add-token screen from unlock screen.')
|
||||||
return h(AddTokenScreen, {key: 'add-token'})
|
return h(AddTokenScreen, {key: 'add-token'})
|
||||||
|
|
||||||
|
case 'add-suggested-token':
|
||||||
|
log.debug('rendering add-suggested-token screen from unlock screen.')
|
||||||
|
return h(AddSuggestedTokenScreen, {key: 'add-suggested-token'})
|
||||||
|
|
||||||
case 'config':
|
case 'config':
|
||||||
log.debug('rendering config screen')
|
log.debug('rendering config screen')
|
||||||
return h(ConfigScreen, {key: 'config'})
|
return h(ConfigScreen, {key: 'config'})
|
||||||
|
|
|
@ -350,11 +350,14 @@ module.exports = class AppBar extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCommonRpc (rpcList, {rpcTarget}) {
|
renderCommonRpc (rpcList, provider) {
|
||||||
const {dispatch} = this.props
|
const {dispatch} = this.props
|
||||||
|
const reversedRpcList = rpcList.slice().reverse()
|
||||||
|
|
||||||
return rpcList.map((rpc) => {
|
return reversedRpcList.map((rpc) => {
|
||||||
if ((rpc === LOCALHOST_RPC_URL) || (rpc === rpcTarget)) {
|
const currentRpcTarget = provider.type === 'rpc' && rpc === provider.rpcTarget
|
||||||
|
|
||||||
|
if ((rpc === LOCALHOST_RPC_URL) || currentRpcTarget) {
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
return h(DropdownMenuItem, {
|
return h(DropdownMenuItem, {
|
||||||
|
@ -364,7 +367,7 @@ module.exports = class AppBar extends Component {
|
||||||
}, [
|
}, [
|
||||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||||
rpc,
|
rpc,
|
||||||
rpcTarget === rpc
|
currentRpcTarget
|
||||||
? h('.check', '✓')
|
? h('.check', '✓')
|
||||||
: null,
|
: null,
|
||||||
])
|
])
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ PendingMsgDetails.prototype.render = function () {
|
||||||
var identity = state.identities[address] || { address: address }
|
var identity = state.identities[address] || { address: address }
|
||||||
var account = state.accounts[address] || { address: address }
|
var account = state.accounts[address] || { address: address }
|
||||||
|
|
||||||
var { data } = msgParams
|
var { data, version } = msgParams
|
||||||
|
|
||||||
return (
|
return (
|
||||||
h('div', {
|
h('div', {
|
||||||
|
@ -48,6 +48,7 @@ PendingMsgDetails.prototype.render = function () {
|
||||||
h('label.font-small', { style: { display: 'block' } }, 'YOU ARE SIGNING'),
|
h('label.font-small', { style: { display: 'block' } }, 'YOU ARE SIGNING'),
|
||||||
h(TypedMessageRenderer, {
|
h(TypedMessageRenderer, {
|
||||||
value: data,
|
value: data,
|
||||||
|
version,
|
||||||
style: {
|
style: {
|
||||||
height: '215px',
|
height: '215px',
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,6 +2,7 @@ const Component = require('react').Component
|
||||||
const h = require('react-hyperscript')
|
const h = require('react-hyperscript')
|
||||||
const inherits = require('util').inherits
|
const inherits = require('util').inherits
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
|
const { ObjectInspector } = require('react-inspector')
|
||||||
|
|
||||||
module.exports = TypedMessageRenderer
|
module.exports = TypedMessageRenderer
|
||||||
|
|
||||||
|
@ -12,8 +13,16 @@ function TypedMessageRenderer () {
|
||||||
|
|
||||||
TypedMessageRenderer.prototype.render = function () {
|
TypedMessageRenderer.prototype.render = function () {
|
||||||
const props = this.props
|
const props = this.props
|
||||||
const { value, style } = props
|
const { value, version, style } = props
|
||||||
const text = renderTypedData(value)
|
let text
|
||||||
|
switch (version) {
|
||||||
|
case 'V1':
|
||||||
|
text = renderTypedData(value)
|
||||||
|
break
|
||||||
|
case 'V3':
|
||||||
|
text = renderTypedDataV3(value)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
const defaultStyle = extend({
|
const defaultStyle = extend({
|
||||||
width: '315px',
|
width: '315px',
|
||||||
|
@ -44,3 +53,17 @@ function renderTypedData (values) {
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderTypedDataV3 (values) {
|
||||||
|
const { domain, message } = JSON.parse(values)
|
||||||
|
return [
|
||||||
|
domain ? h('div', [
|
||||||
|
h('h1', 'Domain'),
|
||||||
|
h(ObjectInspector, { data: domain, expandLevel: 1, name: 'domain' }),
|
||||||
|
]) : '',
|
||||||
|
message ? h('div', [
|
||||||
|
h('h1', 'Message'),
|
||||||
|
h(ObjectInspector, { data: message, expandLevel: 1, name: 'message' }),
|
||||||
|
]) : '',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
|
@ -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!'),
|
||||||
]),
|
]),
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
38
package.json
38
package.json
|
@ -8,6 +8,7 @@
|
||||||
"mascara": "gulp dev:mascara & node ./mascara/example/server",
|
"mascara": "gulp dev:mascara & node ./mascara/example/server",
|
||||||
"dist": "gulp dist",
|
"dist": "gulp dist",
|
||||||
"doc": "jsdoc -c development/tools/.jsdoc.json",
|
"doc": "jsdoc -c development/tools/.jsdoc.json",
|
||||||
|
"publish-docs": "gh-pages -d docs/jsdocs",
|
||||||
"test": "npm run test:unit && npm run test:integration && npm run lint",
|
"test": "npm run test:unit && npm run test:integration && npm run lint",
|
||||||
"watch:test:unit": "nodemon --exec \"npm run test:unit\" ./test ./app ./ui",
|
"watch:test:unit": "nodemon --exec \"npm run test:unit\" ./test ./app ./ui",
|
||||||
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" \"ui/app/**/*.test.js\" && dot-only-hunter",
|
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" \"ui/app/**/*.test.js\" && dot-only-hunter",
|
||||||
|
@ -22,7 +23,7 @@
|
||||||
"test:e2e:run:firefox": "SELENIUM_BROWSER=firefox mocha test/e2e/metamask.spec --bail --recursive",
|
"test:e2e:run:firefox": "SELENIUM_BROWSER=firefox mocha test/e2e/metamask.spec --bail --recursive",
|
||||||
"test:screens": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:screens:run'",
|
"test:screens": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:screens:run'",
|
||||||
"test:screens:run": "node test/screens/new-ui.js",
|
"test:screens:run": "node test/screens/new-ui.js",
|
||||||
"test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload",
|
"test:coverage": "nyc --reporter=text --reporter=html npm run test:unit && npm run test:coveralls-upload",
|
||||||
"test:coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi",
|
"test:coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi",
|
||||||
"test:flat": "npm run test:flat:build && karma start test/flat.conf.js",
|
"test:flat": "npm run test:flat:build && karma start test/flat.conf.js",
|
||||||
"test:flat:build": "npm run test:flat:build:ui && npm run test:flat:build:tests && npm run test:flat:build:locales",
|
"test:flat:build": "npm run test:flat:build:ui && npm run test:flat:build:tests && npm run test:flat:build:locales",
|
||||||
|
@ -36,7 +37,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",
|
||||||
|
@ -57,7 +58,11 @@
|
||||||
[
|
[
|
||||||
"env",
|
"env",
|
||||||
{
|
{
|
||||||
"debug": true
|
"browsers": [
|
||||||
|
">0.25%",
|
||||||
|
"not ie 11",
|
||||||
|
"not op_mini all"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"stage-0"
|
"stage-0"
|
||||||
|
@ -105,15 +110,19 @@
|
||||||
"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-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-json-rpc-middleware": "^2.4.0",
|
||||||
|
"eth-keyring-controller": "^3.1.4",
|
||||||
|
"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",
|
||||||
"eth-sig-util": "^1.4.2",
|
"eth-sig-util": "^2.0.2",
|
||||||
"eth-token-tracker": "^1.1.4",
|
"eth-token-tracker": "^1.1.4",
|
||||||
"eth-trezor-keyring": "^0.1.0",
|
"eth-trezor-keyring": "^0.1.0",
|
||||||
"ethereumjs-abi": "^0.6.4",
|
"ethereumjs-abi": "^0.6.4",
|
||||||
|
@ -127,6 +136,7 @@
|
||||||
"ethjs-query": "^0.3.4",
|
"ethjs-query": "^0.3.4",
|
||||||
"express": "^4.15.5",
|
"express": "^4.15.5",
|
||||||
"extension-link-enabler": "^1.0.0",
|
"extension-link-enabler": "^1.0.0",
|
||||||
|
"extension-port-stream": "^1.0.0",
|
||||||
"extensionizer": "^1.0.1",
|
"extensionizer": "^1.0.1",
|
||||||
"fast-json-patch": "^2.0.4",
|
"fast-json-patch": "^2.0.4",
|
||||||
"fast-levenshtein": "^2.0.6",
|
"fast-levenshtein": "^2.0.6",
|
||||||
|
@ -138,28 +148,30 @@
|
||||||
"gulp-eslint": "^4.0.0",
|
"gulp-eslint": "^4.0.0",
|
||||||
"gulp-sass": "^4.0.0",
|
"gulp-sass": "^4.0.0",
|
||||||
"hat": "0.0.3",
|
"hat": "0.0.3",
|
||||||
"human-standard-token-abi": "^1.0.2",
|
"human-standard-token-abi": "^2.0.0",
|
||||||
"idb-global": "^2.1.0",
|
"idb-global": "^2.1.0",
|
||||||
"identicon.js": "^2.3.1",
|
"identicon.js": "^2.3.1",
|
||||||
"iframe": "^1.0.0",
|
"iframe": "^1.0.0",
|
||||||
"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",
|
||||||
|
"jsonschema": "^1.2.4",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"lodash.memoize": "^4.1.2",
|
"lodash.memoize": "^4.1.2",
|
||||||
"lodash.shuffle": "^4.2.0",
|
"lodash.shuffle": "^4.2.0",
|
||||||
"lodash.uniqby": "^4.7.0",
|
"lodash.uniqby": "^4.7.0",
|
||||||
"loglevel": "^1.4.1",
|
"loglevel": "^1.4.1",
|
||||||
"metamascara": "^2.0.0",
|
"metamascara": "^2.0.0",
|
||||||
|
"metamask-inpage-provider": "^1.0.0",
|
||||||
"metamask-logo": "^2.1.4",
|
"metamask-logo": "^2.1.4",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"multihashes": "^0.4.12",
|
"multihashes": "^0.4.12",
|
||||||
"multiplex": "^6.7.0",
|
"multiplex": "^6.7.0",
|
||||||
"number-to-bn": "^1.7.0",
|
"number-to-bn": "^1.7.0",
|
||||||
"obj-multiplex": "^1.0.0",
|
"obj-multiplex": "^1.0.0",
|
||||||
"obs-store": "^3.0.0",
|
"obs-store": "^3.0.2",
|
||||||
"percentile": "^1.2.0",
|
"percentile": "^1.2.0",
|
||||||
"pify": "^3.0.0",
|
"pify": "^3.0.0",
|
||||||
"ping-pong-stream": "^1.0.0",
|
"ping-pong-stream": "^1.0.0",
|
||||||
|
@ -178,7 +190,9 @@
|
||||||
"react-addons-css-transition-group": "^15.6.0",
|
"react-addons-css-transition-group": "^15.6.0",
|
||||||
"react-dom": "^15.6.2",
|
"react-dom": "^15.6.2",
|
||||||
"react-hyperscript": "^3.0.0",
|
"react-hyperscript": "^3.0.0",
|
||||||
|
"react-inspector": "^2.3.0",
|
||||||
"react-markdown": "^3.0.0",
|
"react-markdown": "^3.0.0",
|
||||||
|
"react-media": "^1.8.0",
|
||||||
"react-redux": "^5.0.5",
|
"react-redux": "^5.0.5",
|
||||||
"react-router-dom": "^4.2.2",
|
"react-router-dom": "^4.2.2",
|
||||||
"react-select": "^1.0.0",
|
"react-select": "^1.0.0",
|
||||||
|
@ -202,11 +216,11 @@
|
||||||
"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.1.0",
|
||||||
"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",
|
"webrtc-adapter": "^6.3.0",
|
||||||
"xtend": "^4.0.1"
|
"xtend": "^4.0.1"
|
||||||
|
@ -240,8 +254,8 @@
|
||||||
"del": "^3.0.0",
|
"del": "^3.0.0",
|
||||||
"dot-only-hunter": "^1.0.3",
|
"dot-only-hunter": "^1.0.3",
|
||||||
"envify": "^4.0.0",
|
"envify": "^4.0.0",
|
||||||
"enzyme": "^3.3.0",
|
"enzyme": "^3.4.4",
|
||||||
"enzyme-adapter-react-15": "^1.0.5",
|
"enzyme-adapter-react-15": "^1.0.6",
|
||||||
"eslint-plugin-chai": "0.0.1",
|
"eslint-plugin-chai": "0.0.1",
|
||||||
"eslint-plugin-json": "^1.2.0",
|
"eslint-plugin-json": "^1.2.0",
|
||||||
"eslint-plugin-mocha": "^5.0.0",
|
"eslint-plugin-mocha": "^5.0.0",
|
||||||
|
@ -249,6 +263,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",
|
||||||
|
@ -293,6 +308,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",
|
||||||
|
|
|
@ -314,12 +314,12 @@ describe('Using MetaMask with an existing account', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('finds the transaction in the transactions list', async function () {
|
it('finds the transaction in the transactions list', async function () {
|
||||||
const transactions = await findElements(driver, By.css('.tx-list-item'))
|
const transactions = await findElements(driver, By.css('.transaction-list-item'))
|
||||||
assert.equal(transactions.length, 1)
|
assert.equal(transactions.length, 1)
|
||||||
|
|
||||||
const txValues = await findElements(driver, By.css('.tx-list-value'))
|
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||||
assert.equal(txValues.length, 1)
|
assert.equal(txValues.length, 1)
|
||||||
assert.equal(await txValues[0].getText(), '1 ETH')
|
assert.equal(await txValues[0].getText(), '-1 ETH')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -366,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,
|
||||||
|
|
|
@ -225,19 +225,9 @@ describe('MetaMask', function () {
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
await clickWordAndWait(words[0])
|
for (let i = 0; i < 12; i++) {
|
||||||
await clickWordAndWait(words[1])
|
await clickWordAndWait(words[i])
|
||||||
await clickWordAndWait(words[2])
|
}
|
||||||
await clickWordAndWait(words[3])
|
|
||||||
await clickWordAndWait(words[4])
|
|
||||||
await clickWordAndWait(words[5])
|
|
||||||
await clickWordAndWait(words[6])
|
|
||||||
await clickWordAndWait(words[7])
|
|
||||||
await clickWordAndWait(words[8])
|
|
||||||
await clickWordAndWait(words[9])
|
|
||||||
await clickWordAndWait(words[10])
|
|
||||||
await clickWordAndWait(words[11])
|
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (count > 2) {
|
if (count > 2) {
|
||||||
throw e
|
throw e
|
||||||
|
@ -414,12 +404,12 @@ describe('MetaMask', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('finds the transaction in the transactions list', async function () {
|
it('finds the transaction in the transactions list', async function () {
|
||||||
const transactions = await findElements(driver, By.css('.tx-list-item'))
|
const transactions = await findElements(driver, By.css('.transaction-list-item'))
|
||||||
assert.equal(transactions.length, 1)
|
assert.equal(transactions.length, 1)
|
||||||
|
|
||||||
if (process.env.SELENIUM_BROWSER !== 'firefox') {
|
if (process.env.SELENIUM_BROWSER !== 'firefox') {
|
||||||
const txValues = await findElement(driver, By.css('.tx-list-value'))
|
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary'))
|
||||||
await driver.wait(until.elementTextMatches(txValues, /1\sETH/), 10000)
|
await driver.wait(until.elementTextMatches(txValues, /-1\sETH/), 10000)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -457,16 +447,11 @@ describe('MetaMask', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('finds the transaction in the transactions list', async function () {
|
it('finds the transaction in the transactions list', async function () {
|
||||||
const transactions = await findElements(driver, By.css('.tx-list-item'))
|
const transactions = await findElements(driver, By.css('.transaction-list-item'))
|
||||||
assert.equal(transactions.length, 2)
|
assert.equal(transactions.length, 2)
|
||||||
|
|
||||||
await findElement(driver, By.xpath(`//span[contains(text(), 'Submitted')]`))
|
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary'))
|
||||||
|
await driver.wait(until.elementTextMatches(txValues, /-3\sETH/), 10000)
|
||||||
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'))
|
|
||||||
await driver.wait(until.elementTextMatches(txValues, /3\sETH/), 10000)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -489,9 +474,9 @@ describe('MetaMask', function () {
|
||||||
await driver.switchTo().window(extension)
|
await driver.switchTo().window(extension)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const txListItem = await findElement(driver, By.xpath(`//span[contains(text(), 'Contract Deployment')]`))
|
const txListItem = await findElement(driver, By.xpath(`//div[contains(text(), 'Contract Deployment')]`))
|
||||||
await txListItem.click()
|
await txListItem.click()
|
||||||
await delay(regularDelayMs)
|
await delay(largeDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('displays the contract creation data', async () => {
|
it('displays the contract creation data', async () => {
|
||||||
|
@ -513,15 +498,15 @@ describe('MetaMask', function () {
|
||||||
it('confirms a deploy contract transaction', async () => {
|
it('confirms a deploy contract transaction', async () => {
|
||||||
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
|
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||||
await confirmButton.click()
|
await confirmButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(largeDelayMs)
|
||||||
|
|
||||||
await findElement(driver, By.xpath(`//span[contains(text(), 'Submitted')]`))
|
driver.wait(async () => {
|
||||||
|
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
|
||||||
|
return confirmedTxes.length === 3
|
||||||
|
}, 10000)
|
||||||
|
|
||||||
const txStatuses = await findElements(driver, By.css('.tx-list-status'))
|
const txAction = await findElements(driver, By.css('.transaction-list-item__action'))
|
||||||
await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed/))
|
await driver.wait(until.elementTextMatches(txAction[0], /Contract\sDeployment/), 10000)
|
||||||
|
|
||||||
const txAccounts = await findElements(driver, By.css('.tx-list-account'))
|
|
||||||
assert.equal(await txAccounts[0].getText(), 'Contract Deployment')
|
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -542,9 +527,9 @@ describe('MetaMask', function () {
|
||||||
await driver.switchTo().window(extension)
|
await driver.switchTo().window(extension)
|
||||||
await delay(largeDelayMs)
|
await delay(largeDelayMs)
|
||||||
|
|
||||||
await findElements(driver, By.css('.tx-list-pending-item-container'))
|
await findElements(driver, By.css('.transaction-list-item'))
|
||||||
const [txListValue] = await findElements(driver, By.css('.tx-list-value'))
|
const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||||
await driver.wait(until.elementTextMatches(txListValue, /4\sETH/), 10000)
|
await driver.wait(until.elementTextMatches(txListValue, /-4\sETH/), 10000)
|
||||||
await txListValue.click()
|
await txListValue.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
@ -572,15 +557,17 @@ describe('MetaMask', function () {
|
||||||
await confirmButton.click()
|
await confirmButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const txStatuses = await findElements(driver, By.css('.tx-list-status'))
|
driver.wait(async () => {
|
||||||
await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed/))
|
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
|
||||||
|
return confirmedTxes.length === 4
|
||||||
|
}, 10000)
|
||||||
|
|
||||||
const txValues = await findElement(driver, By.css('.tx-list-value'))
|
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||||
await driver.wait(until.elementTextMatches(txValues, /4\sETH/), 10000)
|
await driver.wait(until.elementTextMatches(txValues[0], /-4\sETH/), 10000)
|
||||||
|
|
||||||
const txAccounts = await findElements(driver, By.css('.tx-list-account'))
|
// const txAccounts = await findElements(driver, By.css('.tx-list-account'))
|
||||||
const firstTxAddress = await txAccounts[0].getText()
|
// const firstTxAddress = await txAccounts[0].getText()
|
||||||
assert(firstTxAddress.match(/^0x\w{8}\.{3}\w{4}$/))
|
// assert(firstTxAddress.match(/^0x\w{8}\.{3}\w{4}$/))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('calls and confirms a contract method where ETH is received', async () => {
|
it('calls and confirms a contract method where ETH is received', async () => {
|
||||||
|
@ -594,7 +581,7 @@ describe('MetaMask', function () {
|
||||||
await driver.switchTo().window(extension)
|
await driver.switchTo().window(extension)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const txListItem = await findElement(driver, By.css('.tx-list-item'))
|
const txListItem = await findElement(driver, By.css('.transaction-list-item'))
|
||||||
await txListItem.click()
|
await txListItem.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
@ -602,18 +589,20 @@ describe('MetaMask', function () {
|
||||||
await confirmButton.click()
|
await confirmButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const txStatuses = await findElements(driver, By.css('.tx-list-status'))
|
driver.wait(async () => {
|
||||||
await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed/))
|
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
|
||||||
|
return confirmedTxes.length === 5
|
||||||
|
}, 10000)
|
||||||
|
|
||||||
const txValues = await findElement(driver, By.css('.tx-list-value'))
|
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary'))
|
||||||
await driver.wait(until.elementTextMatches(txValues, /0\sETH/), 10000)
|
await driver.wait(until.elementTextMatches(txValues, /-0\sETH/), 10000)
|
||||||
|
|
||||||
await closeAllWindowHandlesExcept(driver, [extension, dapp])
|
await closeAllWindowHandlesExcept(driver, [extension, dapp])
|
||||||
await driver.switchTo().window(extension)
|
await driver.switchTo().window(extension)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders the correct ETH balance', async () => {
|
it('renders the correct ETH balance', async () => {
|
||||||
const balance = await findElement(driver, By.css('.tx-view .balance-display .token-amount'))
|
const balance = await findElement(driver, By.css('.transaction-view-balance__primary-balance'))
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
if (process.env.SELENIUM_BROWSER !== 'firefox') {
|
if (process.env.SELENIUM_BROWSER !== 'firefox') {
|
||||||
await driver.wait(until.elementTextMatches(balance, /^92.*ETH.*$/), 10000)
|
await driver.wait(until.elementTextMatches(balance, /^92.*ETH.*$/), 10000)
|
||||||
|
@ -626,20 +615,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')]`))
|
||||||
|
@ -657,18 +647,17 @@ describe('MetaMask', function () {
|
||||||
await closeAllWindowHandlesExcept(driver, [extension, dapp])
|
await closeAllWindowHandlesExcept(driver, [extension, dapp])
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
await driver.switchTo().window(extension)
|
await driver.switchTo().window(extension)
|
||||||
await delay(regularDelayMs)
|
await delay(largeDelayMs)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('clicks on the Add Token button', async () => {
|
it('clicks on the Add Token button', async () => {
|
||||||
const addToken = await findElement(driver, By.xpath(`//button[contains(text(), 'Add Token')]`))
|
const addToken = await driver.findElement(By.css('.wallet-view__add-token-button'))
|
||||||
await addToken.click()
|
await addToken.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('picks the newly created Test token', async () => {
|
it('picks the newly created Test token', async () => {
|
||||||
const addCustomToken = await findElement(driver, By.xpath("//div[contains(text(), 'Custom Token')]"))
|
const addCustomToken = await findElement(driver, By.xpath("//li[contains(text(), 'Custom Token')]"))
|
||||||
await addCustomToken.click()
|
await addCustomToken.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
@ -686,7 +675,7 @@ describe('MetaMask', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders the balance for the new token', async () => {
|
it('renders the balance for the new token', async () => {
|
||||||
const balance = await findElement(driver, By.css('.tx-view .balance-display .token-amount'))
|
const balance = await findElement(driver, By.css('.transaction-view-balance .transaction-view-balance__token-balance'))
|
||||||
await driver.wait(until.elementTextMatches(balance, /^100\s*TST\s*$/))
|
await driver.wait(until.elementTextMatches(balance, /^100\s*TST\s*$/))
|
||||||
const tokenAmount = await balance.getText()
|
const tokenAmount = await balance.getText()
|
||||||
assert.ok(/^100\s*TST\s*$/.test(tokenAmount))
|
assert.ok(/^100\s*TST\s*$/.test(tokenAmount))
|
||||||
|
@ -755,21 +744,25 @@ describe('MetaMask', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('finds the transaction in the transactions list', async function () {
|
it('finds the transaction in the transactions list', async function () {
|
||||||
const transactions = await findElements(driver, By.css('.tx-list-item'))
|
const transactions = await findElements(driver, By.css('.transaction-list-item'))
|
||||||
assert.equal(transactions.length, 1)
|
assert.equal(transactions.length, 1)
|
||||||
|
|
||||||
const txValues = await findElements(driver, By.css('.tx-list-value'))
|
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||||
assert.equal(txValues.length, 1)
|
assert.equal(txValues.length, 1)
|
||||||
|
|
||||||
// test cancelled on firefox until https://github.com/mozilla/geckodriver/issues/906 is resolved,
|
// test cancelled on firefox until https://github.com/mozilla/geckodriver/issues/906 is resolved,
|
||||||
// or possibly until we use latest version of firefox in the tests
|
// or possibly until we use latest version of firefox in the tests
|
||||||
if (process.env.SELENIUM_BROWSER !== 'firefox') {
|
if (process.env.SELENIUM_BROWSER !== 'firefox') {
|
||||||
await driver.wait(until.elementTextMatches(txValues[0], /50\sTST/), 10000)
|
await driver.wait(until.elementTextMatches(txValues[0], /-50\sTST/), 10000)
|
||||||
}
|
}
|
||||||
|
|
||||||
const txStatuses = await findElements(driver, By.css('.tx-list-status'))
|
driver.wait(async () => {
|
||||||
const tx = await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed|Failed/), 10000)
|
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
|
||||||
assert.equal(await tx.getText(), 'Confirmed')
|
return confirmedTxes.length === 1
|
||||||
|
}, 10000)
|
||||||
|
const txStatuses = await findElements(driver, By.css('.transaction-list-item__action'))
|
||||||
|
const tx = await driver.wait(until.elementTextMatches(txStatuses[0], /Sent\sToken|Failed/), 10000)
|
||||||
|
assert.equal(await tx.getText(), 'Sent Tokens')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -792,9 +785,9 @@ describe('MetaMask', function () {
|
||||||
await driver.switchTo().window(extension)
|
await driver.switchTo().window(extension)
|
||||||
await delay(largeDelayMs)
|
await delay(largeDelayMs)
|
||||||
|
|
||||||
await findElements(driver, By.css('.tx-list-pending-item-container'))
|
await findElements(driver, By.css('.transaction-list__pending-transactions'))
|
||||||
const [txListValue] = await findElements(driver, By.css('.tx-list-value'))
|
const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||||
await driver.wait(until.elementTextMatches(txListValue, /7\sTST/), 10000)
|
await driver.wait(until.elementTextMatches(txListValue, /-7\sTST/), 10000)
|
||||||
await txListValue.click()
|
await txListValue.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
@ -841,25 +834,28 @@ describe('MetaMask', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('finds the transaction in the transactions list', async function () {
|
it('finds the transaction in the transactions list', async function () {
|
||||||
const transactions = await findElements(driver, By.css('.tx-list-item'))
|
driver.wait(async () => {
|
||||||
assert.equal(transactions.length, 2)
|
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
|
||||||
|
return confirmedTxes.length === 2
|
||||||
|
}, 10000)
|
||||||
|
|
||||||
const txValues = await findElements(driver, By.css('.tx-list-value'))
|
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||||
await driver.wait(until.elementTextMatches(txValues[0], /7\sTST/))
|
await driver.wait(until.elementTextMatches(txValues[0], /-7\sTST/))
|
||||||
const txStatuses = await findElements(driver, By.css('.tx-list-status'))
|
const txStatuses = await findElements(driver, By.css('.transaction-list-item__action'))
|
||||||
await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed/))
|
await driver.wait(until.elementTextMatches(txStatuses[0], /Sent\sToken/))
|
||||||
|
|
||||||
const walletBalance = await findElement(driver, By.css('.wallet-balance'))
|
const walletBalance = await findElement(driver, By.css('.wallet-balance'))
|
||||||
await walletBalance.click()
|
await walletBalance.click()
|
||||||
|
|
||||||
const tokenListItems = await findElements(driver, By.css('.token-list-item'))
|
const tokenListItems = await findElements(driver, By.css('.token-list-item'))
|
||||||
await tokenListItems[0].click()
|
await tokenListItems[0].click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
// test cancelled on firefox until https://github.com/mozilla/geckodriver/issues/906 is resolved,
|
// test cancelled on firefox until https://github.com/mozilla/geckodriver/issues/906 is resolved,
|
||||||
// or possibly until we use latest version of firefox in the tests
|
// or possibly until we use latest version of firefox in the tests
|
||||||
if (process.env.SELENIUM_BROWSER !== 'firefox') {
|
if (process.env.SELENIUM_BROWSER !== 'firefox') {
|
||||||
const tokenBalanceAmount = await findElement(driver, By.css('.token-balance__amount'))
|
const tokenBalanceAmount = await findElement(driver, By.css('.transaction-view-balance__token-balance'))
|
||||||
assert.equal(await tokenBalanceAmount.getText(), '43')
|
assert.equal(await tokenBalanceAmount.getText(), '43 TST')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -883,9 +879,14 @@ describe('MetaMask', function () {
|
||||||
await driver.switchTo().window(extension)
|
await driver.switchTo().window(extension)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const [txListItem] = await findElements(driver, By.css('.tx-list-item'))
|
driver.wait(async () => {
|
||||||
const [txListValue] = await findElements(driver, By.css('.tx-list-value'))
|
const pendingTxes = await findElements(driver, By.css('.transaction-list__pending-transactions .transaction-list-item'))
|
||||||
await driver.wait(until.elementTextMatches(txListValue, /0\sETH/))
|
return pendingTxes.length === 1
|
||||||
|
}, 10000)
|
||||||
|
|
||||||
|
const [txListItem] = await findElements(driver, By.css('.transaction-list-item'))
|
||||||
|
const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||||
|
await driver.wait(until.elementTextMatches(txListValue, /-7\sTST/))
|
||||||
await txListItem.click()
|
await txListItem.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
@ -956,10 +957,15 @@ describe('MetaMask', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('finds the transaction in the transactions list', async function () {
|
it('finds the transaction in the transactions list', async function () {
|
||||||
const txValues = await findElements(driver, By.css('.tx-list-value'))
|
driver.wait(async () => {
|
||||||
await driver.wait(until.elementTextMatches(txValues[0], /0\sETH/))
|
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
|
||||||
const txStatuses = await findElements(driver, By.css('.tx-list-status'))
|
return confirmedTxes.length === 3
|
||||||
await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed/))
|
}, 10000)
|
||||||
|
|
||||||
|
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||||
|
await driver.wait(until.elementTextMatches(txValues[0], /-7\sTST/))
|
||||||
|
const txStatuses = await findElements(driver, By.css('.transaction-list-item__action'))
|
||||||
|
await driver.wait(until.elementTextMatches(txStatuses[0], /Approve/))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1009,9 +1015,69 @@ describe('MetaMask', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders the balance for the chosen token', async () => {
|
it('renders the balance for the chosen token', async () => {
|
||||||
const balance = await findElement(driver, By.css('.tx-view .balance-display .token-amount'))
|
const balance = await findElement(driver, By.css('.transaction-view-balance__token-balance'))
|
||||||
await driver.wait(until.elementTextMatches(balance, /0\sBAT/))
|
await driver.wait(until.elementTextMatches(balance, /0\sBAT/))
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Stores custom RPC history', () => {
|
||||||
|
const customRpcUrls = [
|
||||||
|
'https://mainnet.infura.io/1',
|
||||||
|
'https://mainnet.infura.io/2',
|
||||||
|
'https://mainnet.infura.io/3',
|
||||||
|
'https://mainnet.infura.io/4',
|
||||||
|
]
|
||||||
|
|
||||||
|
customRpcUrls.forEach(customRpcUrl => {
|
||||||
|
it('creates custom RPC: ' + customRpcUrl, async () => {
|
||||||
|
const networkDropdown = await findElement(driver, By.css('.network-name'))
|
||||||
|
await networkDropdown.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const customRpcButton = await findElement(driver, By.xpath(`//span[contains(text(), 'Custom RPC')]`))
|
||||||
|
await customRpcButton.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const customRpcInput = await findElement(driver, By.css('input[placeholder="New RPC URL"]'))
|
||||||
|
await customRpcInput.clear()
|
||||||
|
await customRpcInput.sendKeys(customRpcUrl)
|
||||||
|
|
||||||
|
const customRpcSave = await findElement(driver, By.css('.settings-tab__rpc-save-button'))
|
||||||
|
await customRpcSave.click()
|
||||||
|
await delay(largeDelayMs * 2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('selects another provider', async () => {
|
||||||
|
const networkDropdown = await findElement(driver, By.css('.network-name'))
|
||||||
|
await networkDropdown.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const customRpcButton = await findElement(driver, By.xpath(`//span[contains(text(), 'Main Ethereum Network')]`))
|
||||||
|
await customRpcButton.click()
|
||||||
|
await delay(largeDelayMs * 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('finds 3 recent RPCs in history', async () => {
|
||||||
|
const networkDropdown = await findElement(driver, By.css('.network-name'))
|
||||||
|
await networkDropdown.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
// oldest selected RPC is not found
|
||||||
|
await assertElementNotPresent(webdriver, driver, By.xpath(`//span[contains(text(), '${customRpcUrls[0]}')]`))
|
||||||
|
|
||||||
|
// only recent 3 are found and in correct order (most recent at the top)
|
||||||
|
const customRpcs = await findElements(driver, By.xpath(`//span[contains(text(), 'https://mainnet.infura.io/')]`))
|
||||||
|
|
||||||
|
assert.equal(customRpcs.length, 3)
|
||||||
|
|
||||||
|
for (let i = 0; i < customRpcs.length; i++) {
|
||||||
|
const linkText = await customRpcs[i].getText()
|
||||||
|
const rpcUrl = customRpcUrls[customRpcUrls.length - i - 1]
|
||||||
|
|
||||||
|
assert.notEqual(linkText.indexOf(rpcUrl), -1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,5 +6,5 @@ set -o pipefail
|
||||||
|
|
||||||
export PATH="$PATH:./node_modules/.bin"
|
export PATH="$PATH:./node_modules/.bin"
|
||||||
|
|
||||||
shell-parallel -s 'npm run ganache:start' -x 'sleep 5 && static-server test/e2e/beta/contract-test/ --port 8080' -x 'sleep 5 && mocha test/e2e/beta/metamask-beta-ui.spec'
|
shell-parallel -s 'npm run ganache:start -- -b 2' -x 'sleep 5 && static-server test/e2e/beta/contract-test/ --port 8080' -x 'sleep 5 && mocha test/e2e/beta/metamask-beta-ui.spec'
|
||||||
shell-parallel -s 'npm run ganache:start -- -d' -x 'sleep 5 && static-server test/e2e/beta/contract-test/ --port 8080' -x 'sleep 5 && mocha test/e2e/beta/from-import-beta-ui.spec'
|
shell-parallel -s 'npm run ganache:start -- -d -b 2' -x 'sleep 5 && static-server test/e2e/beta/contract-test/ --port 8080' -x 'sleep 5 && mocha test/e2e/beta/from-import-beta-ui.spec'
|
||||||
|
|
|
@ -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')()
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,7 @@ async function runAddTokenFlowTest (assert, done) {
|
||||||
$('button.btn-primary.btn--large')[0].click()
|
$('button.btn-primary.btn--large')[0].click()
|
||||||
|
|
||||||
// Verify added token image
|
// Verify added token image
|
||||||
let heroBalance = await queryAsync($, '.hero-balance')
|
let heroBalance = await queryAsync($, '.transaction-view-balance__balance-container')
|
||||||
assert.ok(heroBalance, 'rendered hero balance')
|
assert.ok(heroBalance, 'rendered hero balance')
|
||||||
assert.ok(tokenImageUrl.indexOf(heroBalance.find('img').attr('src')) > -1, 'token added')
|
assert.ok(tokenImageUrl.indexOf(heroBalance.find('img').attr('src')) > -1, 'token added')
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ async function runAddTokenFlowTest (assert, done) {
|
||||||
// $('button.btn-primary--lg')[0].click()
|
// $('button.btn-primary--lg')[0].click()
|
||||||
|
|
||||||
// Verify added token image
|
// Verify added token image
|
||||||
heroBalance = await queryAsync($, '.hero-balance')
|
heroBalance = await queryAsync($, '.transaction-view-balance__balance-container')
|
||||||
assert.ok(heroBalance, 'rendered hero balance')
|
assert.ok(heroBalance, 'rendered hero balance')
|
||||||
assert.ok(heroBalance.find('.identicon')[0], 'token added')
|
assert.ok(heroBalance.find('.identicon')[0], 'token added')
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ async function runConfirmSigRequestsTest (assert, done) {
|
||||||
selectState.val('confirm sig requests')
|
selectState.val('confirm sig requests')
|
||||||
reactTriggerChange(selectState[0])
|
reactTriggerChange(selectState[0])
|
||||||
|
|
||||||
const pendingRequestItem = $.find('.tx-list-item.tx-list-pending-item-container.tx-list-clickable')
|
const pendingRequestItem = $.find('.transaction-list-item .transaction-list-item__grid')
|
||||||
|
|
||||||
if (pendingRequestItem[0]) {
|
if (pendingRequestItem[0]) {
|
||||||
pendingRequestItem[0].click()
|
pendingRequestItem[0].click()
|
||||||
|
|
|
@ -22,8 +22,8 @@ async function runCurrencyLocalizationTest (assert, done) {
|
||||||
await timeout(1000)
|
await timeout(1000)
|
||||||
reactTriggerChange(selectState[0])
|
reactTriggerChange(selectState[0])
|
||||||
await timeout(1000)
|
await timeout(1000)
|
||||||
const txView = await queryAsync($, '.tx-view')
|
const txView = await queryAsync($, '.transaction-view')
|
||||||
const heroBalance = await findAsync($(txView), '.hero-balance')
|
const heroBalance = await findAsync($(txView), '.transaction-view-balance__balance')
|
||||||
const fiatAmount = await findAsync($(heroBalance), '.fiat-amount')
|
const fiatAmount = await findAsync($(heroBalance), '.transaction-view-balance__secondary-balance')
|
||||||
assert.equal(fiatAmount[0].textContent, '₱102,707.97')
|
assert.equal(fiatAmount[0].textContent, '₱102,707.97 PHP')
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ async function runSendFlowTest (assert, done) {
|
||||||
selectState.val('send new ui')
|
selectState.val('send new ui')
|
||||||
reactTriggerChange(selectState[0])
|
reactTriggerChange(selectState[0])
|
||||||
|
|
||||||
const sendScreenButton = await queryAsync($, 'button.btn-primary.hero-balance-button')
|
const sendScreenButton = await queryAsync($, 'button.btn-primary.transaction-view-balance__button')
|
||||||
assert.ok(sendScreenButton[1], 'send screen button present')
|
assert.ok(sendScreenButton[1], 'send screen button present')
|
||||||
sendScreenButton[1].click()
|
sendScreenButton[1].click()
|
||||||
|
|
||||||
|
@ -124,10 +124,10 @@ async function runSendFlowTest (assert, done) {
|
||||||
selectState.val('send edit')
|
selectState.val('send edit')
|
||||||
reactTriggerChange(selectState[0])
|
reactTriggerChange(selectState[0])
|
||||||
|
|
||||||
const confirmFromName = (await queryAsync($, '.sender-to-recipient__sender-name')).first()
|
const confirmFromName = (await queryAsync($, '.sender-to-recipient__name')).first()
|
||||||
assert.equal(confirmFromName[0].textContent, 'Send Account 4', 'confirm screen should show correct from name')
|
assert.equal(confirmFromName[0].textContent, 'Send Account 4', 'confirm screen should show correct from name')
|
||||||
|
|
||||||
const confirmToName = (await queryAsync($, '.sender-to-recipient__recipient-name')).last()
|
const confirmToName = (await queryAsync($, '.sender-to-recipient__name')).last()
|
||||||
assert.equal(confirmToName[0].textContent, 'Send Account 3', 'confirm screen should show correct to name')
|
assert.equal(confirmToName[0].textContent, 'Send Account 3', 'confirm screen should show correct to name')
|
||||||
|
|
||||||
const confirmScreenRowFiats = await queryAsync($, '.confirm-detail-row__fiat')
|
const confirmScreenRowFiats = await queryAsync($, '.confirm-detail-row__fiat')
|
||||||
|
|
|
@ -29,26 +29,25 @@ async function runTxListItemsTest (assert, done) {
|
||||||
assert.ok(metamaskLogo[0], 'metamask logo present')
|
assert.ok(metamaskLogo[0], 'metamask logo present')
|
||||||
metamaskLogo[0].click()
|
metamaskLogo[0].click()
|
||||||
|
|
||||||
const txListItems = await queryAsync($, '.tx-list-item')
|
const txListItems = await queryAsync($, '.transaction-list-item')
|
||||||
assert.equal(txListItems.length, 8, 'all tx list items are rendered')
|
assert.equal(txListItems.length, 8, 'all tx list items are rendered')
|
||||||
|
|
||||||
const unapprovedTx = txListItems[0]
|
const retryTxGrid = await findAsync($(txListItems[1]), '.transaction-list-item__grid')
|
||||||
assert.equal($(unapprovedTx).hasClass('tx-list-pending-item-container'), true, 'unapprovedTx has the correct class')
|
retryTxGrid[0].click()
|
||||||
|
const retryTxDetails = await findAsync($(txListItems[1]), '.transaction-list-item-details')
|
||||||
const retryTx = txListItems[1]
|
const headerButtons = await findAsync($(retryTxDetails[0]), '.transaction-list-item-details__header-button')
|
||||||
const retryTxLink = await findAsync($(retryTx), '.tx-list-item-retry-container span')
|
assert.equal(headerButtons[0].textContent, 'speed up')
|
||||||
assert.equal(retryTxLink[0].textContent, 'Taking too long? Increase the gas price on your transaction', 'retryTx has expected link')
|
|
||||||
|
|
||||||
const approvedTx = txListItems[2]
|
const approvedTx = txListItems[2]
|
||||||
const approvedTxRenderedStatus = await findAsync($(approvedTx), '.tx-list-status')
|
const approvedTxRenderedStatus = await findAsync($(approvedTx), '.transaction-list-item__status')
|
||||||
assert.equal(approvedTxRenderedStatus[0].textContent, 'Approved', 'approvedTx has correct label')
|
assert.equal(approvedTxRenderedStatus[0].textContent, 'pending', 'approvedTx has correct label')
|
||||||
|
|
||||||
const unapprovedMsg = txListItems[3]
|
const unapprovedMsg = txListItems[3]
|
||||||
const unapprovedMsgDescription = await findAsync($(unapprovedMsg), '.tx-list-account')
|
const unapprovedMsgDescription = await findAsync($(unapprovedMsg), '.transaction-list-item__action')
|
||||||
assert.equal(unapprovedMsgDescription[0].textContent, 'Signature Request', 'unapprovedMsg has correct description')
|
assert.equal(unapprovedMsgDescription[0].textContent, 'Signature Request', 'unapprovedMsg has correct description')
|
||||||
|
|
||||||
const failedTx = txListItems[4]
|
const failedTx = txListItems[4]
|
||||||
const failedTxRenderedStatus = await findAsync($(failedTx), '.tx-list-status')
|
const failedTxRenderedStatus = await findAsync($(failedTx), '.transaction-list-item__status')
|
||||||
assert.equal(failedTxRenderedStatus[0].textContent, 'Failed', 'failedTx has correct label')
|
assert.equal(failedTxRenderedStatus[0].textContent, 'Failed', 'failedTx has correct label')
|
||||||
|
|
||||||
const shapeShiftTx = txListItems[5]
|
const shapeShiftTx = txListItems[5]
|
||||||
|
@ -56,10 +55,10 @@ async function runTxListItemsTest (assert, done) {
|
||||||
assert.equal(shapeShiftTxStatus[0].textContent, 'No deposits received', 'shapeShiftTx has correct status')
|
assert.equal(shapeShiftTxStatus[0].textContent, 'No deposits received', 'shapeShiftTx has correct status')
|
||||||
|
|
||||||
const confirmedTokenTx = txListItems[6]
|
const confirmedTokenTx = txListItems[6]
|
||||||
const confirmedTokenTxAddress = await findAsync($(confirmedTokenTx), '.tx-list-account')
|
const confirmedTokenTxAddress = await findAsync($(confirmedTokenTx), '.transaction-list-item__status')
|
||||||
assert.equal(confirmedTokenTxAddress[0].textContent, '0xE7884118...81a9', 'confirmedTokenTx has correct address')
|
assert.equal(confirmedTokenTxAddress[0].textContent, 'Confirmed', 'confirmedTokenTx has correct address')
|
||||||
|
|
||||||
const rejectedTx = txListItems[7]
|
const rejectedTx = txListItems[7]
|
||||||
const rejectedTxRenderedStatus = await findAsync($(rejectedTx), '.tx-list-status')
|
const rejectedTxRenderedStatus = await findAsync($(rejectedTx), '.transaction-list-item__status')
|
||||||
assert.equal(rejectedTxRenderedStatus[0].textContent, 'Rejected', 'rejectedTx has correct label')
|
assert.equal(rejectedTxRenderedStatus[0].textContent, 'Rejected', 'rejectedTx has correct label')
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,9 +0,0 @@
|
||||||
const ObservableStore = require('obs-store')
|
|
||||||
const clone = require('clone')
|
|
||||||
const ConfigManager = require('../../app/scripts/lib/config-manager')
|
|
||||||
const firstTimeState = require('../../app/scripts/first-time-state')
|
|
||||||
|
|
||||||
module.exports = function () {
|
|
||||||
const store = new ObservableStore(clone(firstTimeState))
|
|
||||||
return new ConfigManager({ store })
|
|
||||||
}
|
|
|
@ -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')
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue