mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-24 02:58:09 +01:00
commit
dc5477be3c
@ -9,15 +9,26 @@ workflows:
|
|||||||
- prep-build:
|
- prep-build:
|
||||||
requires:
|
requires:
|
||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
|
- prep-docs:
|
||||||
|
requires:
|
||||||
|
- prep-deps-npm
|
||||||
- prep-scss:
|
- prep-scss:
|
||||||
requires:
|
requires:
|
||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
- test-lint:
|
- test-lint:
|
||||||
requires:
|
requires:
|
||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
- test-e2e:
|
- test-deps:
|
||||||
requires:
|
requires:
|
||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
|
- test-e2e-chrome:
|
||||||
|
requires:
|
||||||
|
- prep-deps-npm
|
||||||
|
- prep-build
|
||||||
|
- test-e2e-firefox:
|
||||||
|
requires:
|
||||||
|
- prep-deps-npm
|
||||||
|
- prep-deps-firefox
|
||||||
- prep-build
|
- prep-build
|
||||||
- test-unit:
|
- test-unit:
|
||||||
requires:
|
requires:
|
||||||
@ -44,7 +55,8 @@ workflows:
|
|||||||
requires:
|
requires:
|
||||||
- test-lint
|
- test-lint
|
||||||
- test-unit
|
- test-unit
|
||||||
- test-e2e
|
- test-e2e-chrome
|
||||||
|
- test-e2e-firefox
|
||||||
- test-integration-mascara-chrome
|
- test-integration-mascara-chrome
|
||||||
- test-integration-mascara-firefox
|
- test-integration-mascara-firefox
|
||||||
- test-integration-flat-chrome
|
- test-integration-flat-chrome
|
||||||
@ -54,12 +66,22 @@ workflows:
|
|||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
- prep-build
|
- prep-build
|
||||||
- all-tests-pass
|
- all-tests-pass
|
||||||
- job-publish:
|
- job-publish-prerelease:
|
||||||
requires:
|
requires:
|
||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
- prep-build
|
- prep-build
|
||||||
- job-screens
|
- job-screens
|
||||||
- all-tests-pass
|
- all-tests-pass
|
||||||
|
- job-publish-release:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: master
|
||||||
|
requires:
|
||||||
|
- prep-deps-npm
|
||||||
|
- prep-build
|
||||||
|
- prep-docs
|
||||||
|
- job-screens
|
||||||
|
- all-tests-pass
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
prep-deps-npm:
|
prep-deps-npm:
|
||||||
@ -115,6 +137,21 @@ jobs:
|
|||||||
- dist
|
- dist
|
||||||
- builds
|
- builds
|
||||||
|
|
||||||
|
prep-docs:
|
||||||
|
docker:
|
||||||
|
- image: circleci/node:8-browsers
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: dependency-cache-{{ .Revision }}
|
||||||
|
- run:
|
||||||
|
name: build:dist
|
||||||
|
command: npm run doc
|
||||||
|
- save_cache:
|
||||||
|
key: docs-cache-{{ .Revision }}
|
||||||
|
paths:
|
||||||
|
- docs/jsdoc
|
||||||
|
|
||||||
prep-scss:
|
prep-scss:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:8-browsers
|
- image: circleci/node:8-browsers
|
||||||
@ -145,7 +182,18 @@ jobs:
|
|||||||
name: Test
|
name: Test
|
||||||
command: npm run lint
|
command: npm run lint
|
||||||
|
|
||||||
test-e2e:
|
test-deps:
|
||||||
|
docker:
|
||||||
|
- image: circleci/node:8-browsers
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: dependency-cache-{{ .Revision }}
|
||||||
|
- run:
|
||||||
|
name: Test
|
||||||
|
command: npx nsp check
|
||||||
|
|
||||||
|
test-e2e-chrome:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:8-browsers
|
- image: circleci/node:8-browsers
|
||||||
steps:
|
steps:
|
||||||
@ -156,7 +204,34 @@ jobs:
|
|||||||
key: build-cache-{{ .Revision }}
|
key: build-cache-{{ .Revision }}
|
||||||
- run:
|
- run:
|
||||||
name: Test
|
name: Test
|
||||||
command: npm run test:e2e
|
command: npm run test:e2e:chrome
|
||||||
|
- store_artifacts:
|
||||||
|
path: test-artifacts
|
||||||
|
destination: test-artifacts
|
||||||
|
|
||||||
|
test-e2e-firefox:
|
||||||
|
environment:
|
||||||
|
browsers: '["Firefox"]'
|
||||||
|
docker:
|
||||||
|
- image: circleci/node:8-browsers
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: dependency-cache-firefox-{{ .Revision }}
|
||||||
|
- run:
|
||||||
|
name: Install firefox
|
||||||
|
command: >
|
||||||
|
sudo rm -r /opt/firefox
|
||||||
|
&& sudo mv firefox /opt/firefox58
|
||||||
|
&& sudo mv /usr/bin/firefox /usr/bin/firefox-old
|
||||||
|
&& sudo ln -s /opt/firefox58/firefox /usr/bin/firefox
|
||||||
|
- restore_cache:
|
||||||
|
key: dependency-cache-{{ .Revision }}
|
||||||
|
- restore_cache:
|
||||||
|
key: build-cache-{{ .Revision }}
|
||||||
|
- run:
|
||||||
|
name: test:e2e:firefox
|
||||||
|
command: npm run test:e2e:firefox
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: test-artifacts
|
path: test-artifacts
|
||||||
destination: test-artifacts
|
destination: test-artifacts
|
||||||
@ -178,7 +253,7 @@ jobs:
|
|||||||
paths:
|
paths:
|
||||||
- test-artifacts
|
- test-artifacts
|
||||||
|
|
||||||
job-publish:
|
job-publish-prerelease:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:8-browsers
|
- image: circleci/node:8-browsers
|
||||||
steps:
|
steps:
|
||||||
@ -204,9 +279,29 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: build:announce
|
name: build:announce
|
||||||
command: ./development/metamaskbot-build-announce.js
|
command: ./development/metamaskbot-build-announce.js
|
||||||
|
|
||||||
|
job-publish-release:
|
||||||
|
docker:
|
||||||
|
- image: circleci/node:8-browsers
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: dependency-cache-{{ .Revision }}
|
||||||
|
- 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
|
||||||
|
- run:
|
||||||
|
name: github gh-pages docs publish
|
||||||
|
command: >
|
||||||
|
git config user.name metamaskbot
|
||||||
|
git config user.email admin@metamask.io
|
||||||
|
gh-pages -d docs/jsdocs
|
||||||
|
|
||||||
test-unit:
|
test-unit:
|
||||||
docker:
|
docker:
|
||||||
@ -320,3 +415,4 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: All Tests Passed
|
name: All Tests Passed
|
||||||
command: echo 'weew - everything passed!'
|
command: echo 'weew - everything passed!'
|
||||||
|
|
||||||
|
5
.nsprc
5
.nsprc
@ -1,3 +1,6 @@
|
|||||||
{
|
{
|
||||||
"exceptions": ["https://nodesecurity.io/advisories/566"]
|
"exceptions": [
|
||||||
|
"https://nodesecurity.io/advisories/566",
|
||||||
|
"https://nodesecurity.io/advisories/157"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
10
.storybook/README.md
Normal file
10
.storybook/README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Storybook
|
||||||
|
We're currently using [Storybook](https://storybook.js.org/) as part of our design system. To run Storybook and test some of our UI components, clone the repo and run the following:
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
npm run storybook
|
||||||
|
```
|
||||||
|
You should then see:
|
||||||
|
> info Storybook started on => http://localhost:6006/
|
||||||
|
|
||||||
|
In your browser, navigate to http://localhost:6006/ to see the Storybook application. From here, you'll be able to easily view components and even modify some of their properties.
|
2
.storybook/addons.js
Normal file
2
.storybook/addons.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import '@storybook/addon-knobs/register'
|
||||||
|
import '@storybook/addon-actions/register'
|
11
.storybook/config.js
Normal file
11
.storybook/config.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { configure } from '@storybook/react'
|
||||||
|
import '../ui/app/css/index.scss'
|
||||||
|
|
||||||
|
const req = require.context('../ui/app/components', true, /\.stories\.js$/)
|
||||||
|
|
||||||
|
function loadStories () {
|
||||||
|
require('./decorators')
|
||||||
|
req.keys().forEach((filename) => req(filename))
|
||||||
|
}
|
||||||
|
|
||||||
|
configure(loadStories, module)
|
21
.storybook/decorators.js
Normal file
21
.storybook/decorators.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { addDecorator } from '@storybook/react'
|
||||||
|
import { withInfo } from '@storybook/addon-info'
|
||||||
|
import { withKnobs } from '@storybook/addon-knobs/react'
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
height: '100vh',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}
|
||||||
|
|
||||||
|
const CenterDecorator = story => (
|
||||||
|
<div style={styles}>
|
||||||
|
{ story() }
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
addDecorator((story, context) => withInfo()(story)(context))
|
||||||
|
addDecorator(withKnobs)
|
||||||
|
addDecorator(CenterDecorator)
|
37
.storybook/webpack.config.js
Normal file
37
.storybook/webpack.config.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.(woff(2)?|ttf|eot|svg|otf)(\?v=\d+\.\d+\.\d+)?$/,
|
||||||
|
loaders: [{
|
||||||
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
name: '[name].[ext]',
|
||||||
|
outputPath: 'fonts/',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$/,
|
||||||
|
loaders: [
|
||||||
|
'style-loader',
|
||||||
|
'css-loader',
|
||||||
|
'resolve-url-loader',
|
||||||
|
{
|
||||||
|
loader: 'sass-loader',
|
||||||
|
options: {
|
||||||
|
sourceMap: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'./fonts/Font_Awesome': path.resolve(__dirname, '../fonts/Font_Awesome'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
16
CHANGELOG.md
16
CHANGELOG.md
@ -2,6 +2,20 @@
|
|||||||
|
|
||||||
## Current Master
|
## Current Master
|
||||||
|
|
||||||
|
## 4.7.0 Wed May 30 2018
|
||||||
|
|
||||||
|
- Fix Brave support
|
||||||
|
- Adds error messages when passwords don't match in onboarding flow.
|
||||||
|
- Adds modal notification if a retry in the process of being confirmed is dropped.
|
||||||
|
- New unlock screen design.
|
||||||
|
- Design improvements to the add token screen.
|
||||||
|
- Fix inconsistencies in confirm screen between extension and browser window modes.
|
||||||
|
- Fix scrolling in deposit ether modal.
|
||||||
|
- Fix styling of app spinner.
|
||||||
|
- Font weight changed from 300 to 400.
|
||||||
|
- New reveal screen design.
|
||||||
|
- Styling improvements to labels in first time flow and signature request headers.
|
||||||
|
|
||||||
## 4.6.1 Mon Apr 30 2018
|
## 4.6.1 Mon Apr 30 2018
|
||||||
|
|
||||||
- Fix bug where sending a transaction resulted in an infinite spinner
|
- Fix bug where sending a transaction resulted in an infinite spinner
|
||||||
@ -17,6 +31,8 @@
|
|||||||
- Fetch token prices based on contract address, not symbol
|
- Fetch token prices based on contract address, not symbol
|
||||||
- Fix bug that prevents setting language locale in settings.
|
- Fix bug that prevents setting language locale in settings.
|
||||||
- Show checksum addresses throughout the UI
|
- Show checksum addresses throughout the UI
|
||||||
|
- Allow transactions with a 0 gwei gas price
|
||||||
|
- Made provider RPC errors contain useful messages
|
||||||
|
|
||||||
## 4.5.5 Fri Apr 06 2018
|
## 4.5.5 Fri Apr 06 2018
|
||||||
|
|
||||||
|
14
MISSION.md
Normal file
14
MISSION.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# MetaMask Philosophy
|
||||||
|
|
||||||
|
## Mission
|
||||||
|
|
||||||
|
Making it safe and easy for the most people to use the decentralized web to the greatest degree that is empowering to them.
|
||||||
|
|
||||||
|
## Vision
|
||||||
|
|
||||||
|
To realize the highest goals achievable for the human race with the twin powers of peer to peer networks and cryptography. To empower users to hold and use their own keys on these new networks as securely and intelligibly as possible, enabling a new world of peer to peer agreements and economies, in hopes that we may collectively overcome the many great problems that we face together, through the power of strong cooperation.
|
||||||
|
|
||||||
|
## Strategy
|
||||||
|
|
||||||
|
We provide software for users to manage accounts, for sites to easily propose actions to users, and for users to coherently review actions before approving them. We build on this rapidly evolving set of protocols with the goal of empowering the most people to the greatest degree, and aspire to continuously evolve our offering to pursue that goal.
|
||||||
|
|
@ -1,12 +1,16 @@
|
|||||||
# MetaMask Browser Extension
|
# MetaMask Browser Extension
|
||||||
[![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master) [![Greenkeeper badge](https://badges.greenkeeper.io/MetaMask/metamask-extension.svg)](https://greenkeeper.io/) [![Stories in Ready](https://badge.waffle.io/MetaMask/metamask-extension.png?label=in%20progress&title=waffle.io)](https://waffle.io/MetaMask/metamask-extension)
|
[![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master) [![Greenkeeper badge](https://badges.greenkeeper.io/MetaMask/metamask-extension.svg)](https://greenkeeper.io/) [![Stories in Ready](https://badge.waffle.io/MetaMask/metamask-extension.png?label=in%20progress&title=waffle.io)](https://waffle.io/MetaMask/metamask-extension)
|
||||||
|
|
||||||
[Internal documentation](./docs/jsdocs)
|
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
If you're a user seeking support, [here is our support site](https://metamask.helpscoutdocs.com/).
|
If you're a user seeking support, [here is our support site](https://metamask.helpscoutdocs.com/).
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
[Mission Statement](./MISSION.md)
|
||||||
|
|
||||||
|
[Internal documentation](./docs/jsdocs)
|
||||||
|
|
||||||
## Developing Compatible Dapps
|
## Developing Compatible Dapps
|
||||||
|
|
||||||
If you're a web dapp developer, we've got two types of guides for you:
|
If you're a web dapp developer, we've got two types of guides for you:
|
||||||
|
@ -23,6 +23,9 @@
|
|||||||
"addTokens": {
|
"addTokens": {
|
||||||
"message": "Add Tokens"
|
"message": "Add Tokens"
|
||||||
},
|
},
|
||||||
|
"addAcquiredTokens": {
|
||||||
|
"message": "Add the tokens you've acquired using MetaMask"
|
||||||
|
},
|
||||||
"amount": {
|
"amount": {
|
||||||
"message": "Amount"
|
"message": "Amount"
|
||||||
},
|
},
|
||||||
@ -53,7 +56,7 @@
|
|||||||
"message": "Back"
|
"message": "Back"
|
||||||
},
|
},
|
||||||
"balance": {
|
"balance": {
|
||||||
"message": "Balance:"
|
"message": "Balance"
|
||||||
},
|
},
|
||||||
"balances": {
|
"balances": {
|
||||||
"message": "Token balance(s)"
|
"message": "Token balance(s)"
|
||||||
@ -393,9 +396,15 @@
|
|||||||
"message": "Imported",
|
"message": "Imported",
|
||||||
"description": "status showing that an account has been fully loaded into the keyring"
|
"description": "status showing that an account has been fully loaded into the keyring"
|
||||||
},
|
},
|
||||||
|
"importUsingSeed": {
|
||||||
|
"message": "Import using account seed phrase"
|
||||||
|
},
|
||||||
"infoHelp": {
|
"infoHelp": {
|
||||||
"message": "Info & Help"
|
"message": "Info & Help"
|
||||||
},
|
},
|
||||||
|
"initialTransactionConfirmed": {
|
||||||
|
"message": "Your initial transaction was confirmed by the network. Click OK to go back."
|
||||||
|
},
|
||||||
"insufficientFunds": {
|
"insufficientFunds": {
|
||||||
"message": "Insufficient funds."
|
"message": "Insufficient funds."
|
||||||
},
|
},
|
||||||
@ -632,7 +641,7 @@
|
|||||||
"message": "Reset Account"
|
"message": "Reset Account"
|
||||||
},
|
},
|
||||||
"restoreFromSeed": {
|
"restoreFromSeed": {
|
||||||
"message": "Restore from seed phrase"
|
"message": "Restore account?"
|
||||||
},
|
},
|
||||||
"restoreVault": {
|
"restoreVault": {
|
||||||
"message": "Restore Vault"
|
"message": "Restore Vault"
|
||||||
@ -670,6 +679,9 @@
|
|||||||
"ropsten": {
|
"ropsten": {
|
||||||
"message": "Ropsten Test Network"
|
"message": "Ropsten Test Network"
|
||||||
},
|
},
|
||||||
|
"rpc": {
|
||||||
|
"message": "Custom RPC"
|
||||||
|
},
|
||||||
"currentRpc": {
|
"currentRpc": {
|
||||||
"message": "Current RPC"
|
"message": "Current RPC"
|
||||||
},
|
},
|
||||||
@ -695,10 +707,10 @@
|
|||||||
"save": {
|
"save": {
|
||||||
"message": "Save"
|
"message": "Save"
|
||||||
},
|
},
|
||||||
"reprice_title": {
|
"speedUpTitle": {
|
||||||
"message": "Reprice Transaction"
|
"message": "Speed Up Transaction"
|
||||||
},
|
},
|
||||||
"reprice_subtitle": {
|
"speedUpSubtitle": {
|
||||||
"message": "Increase your gas price to attempt to overwrite and speed up your transaction"
|
"message": "Increase your gas price to attempt to overwrite and speed up your transaction"
|
||||||
},
|
},
|
||||||
"saveAsCsvFile": {
|
"saveAsCsvFile": {
|
||||||
@ -714,6 +726,9 @@
|
|||||||
"search": {
|
"search": {
|
||||||
"message": "Search"
|
"message": "Search"
|
||||||
},
|
},
|
||||||
|
"searchResults": {
|
||||||
|
"message": "Search Results"
|
||||||
|
},
|
||||||
"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."
|
||||||
},
|
},
|
||||||
@ -721,7 +736,7 @@
|
|||||||
"message": "New Password (min 8 chars)"
|
"message": "New Password (min 8 chars)"
|
||||||
},
|
},
|
||||||
"seedPhraseReq": {
|
"seedPhraseReq": {
|
||||||
"message": "seed phrases are 12 words long"
|
"message": "Seed phrases are 12 words long"
|
||||||
},
|
},
|
||||||
"select": {
|
"select": {
|
||||||
"message": "Select"
|
"message": "Select"
|
||||||
@ -829,6 +844,9 @@
|
|||||||
"message": "$1 to ETH via ShapeShift",
|
"message": "$1 to ETH via ShapeShift",
|
||||||
"description": "system will fill in deposit type in start of message"
|
"description": "system will fill in deposit type in start of message"
|
||||||
},
|
},
|
||||||
|
"token": {
|
||||||
|
"message": "Token"
|
||||||
|
},
|
||||||
"tokenAddress": {
|
"tokenAddress": {
|
||||||
"message": "Token Address"
|
"message": "Token Address"
|
||||||
},
|
},
|
||||||
@ -896,6 +914,9 @@
|
|||||||
"unknownNetworkId": {
|
"unknownNetworkId": {
|
||||||
"message": "Unknown network ID"
|
"message": "Unknown network ID"
|
||||||
},
|
},
|
||||||
|
"unlockMessage": {
|
||||||
|
"message": "The decentralized web awaits"
|
||||||
|
},
|
||||||
"uriErrorMsg": {
|
"uriErrorMsg": {
|
||||||
"message": "URIs require the appropriate HTTP/HTTPS prefix."
|
"message": "URIs require the appropriate HTTP/HTTPS prefix."
|
||||||
},
|
},
|
||||||
@ -924,6 +945,9 @@
|
|||||||
"warning": {
|
"warning": {
|
||||||
"message": "Warning"
|
"message": "Warning"
|
||||||
},
|
},
|
||||||
|
"welcomeBack": {
|
||||||
|
"message": "Welcome Back!"
|
||||||
|
},
|
||||||
"welcomeBeta": {
|
"welcomeBeta": {
|
||||||
"message": "Welcome to MetaMask Beta"
|
"message": "Welcome to MetaMask Beta"
|
||||||
},
|
},
|
||||||
|
@ -181,7 +181,7 @@
|
|||||||
"message": "DEN je vaša šifrirana shramba v MetaMasku."
|
"message": "DEN je vaša šifrirana shramba v MetaMasku."
|
||||||
},
|
},
|
||||||
"deposit": {
|
"deposit": {
|
||||||
"message": "Vplačilo"
|
"message": "Vplačaj"
|
||||||
},
|
},
|
||||||
"depositBTC": {
|
"depositBTC": {
|
||||||
"message": "Vplačajte vaš BTC na spodnji naslov:"
|
"message": "Vplačajte vaš BTC na spodnji naslov:"
|
||||||
@ -507,10 +507,10 @@
|
|||||||
"message": "Ni se začelo"
|
"message": "Ni se začelo"
|
||||||
},
|
},
|
||||||
"oldUI": {
|
"oldUI": {
|
||||||
"message": "Starejši uporabniški vmesnik"
|
"message": "Star UI"
|
||||||
},
|
},
|
||||||
"oldUIMessage": {
|
"oldUIMessage": {
|
||||||
"message": "Vrnili ste se v starejši uporabniški vmesnik. V novega se lahko vrnete z možnostjo v spustnem meniju v zgornjem desnem kotu."
|
"message": "Vrnili ste se v star uporabniški vmesnik. V novega se lahko vrnete z možnostjo v spustnem meniju v zgornjem desnem kotu."
|
||||||
},
|
},
|
||||||
"or": {
|
"or": {
|
||||||
"message": "ali",
|
"message": "ali",
|
||||||
@ -759,7 +759,7 @@
|
|||||||
"message": "Vpišite vaše geslo"
|
"message": "Vpišite vaše geslo"
|
||||||
},
|
},
|
||||||
"uiWelcome": {
|
"uiWelcome": {
|
||||||
"message": "Dobrodošli v novem uporabniškem vmesniku (Beta)"
|
"message": "Dobrodošli v nov UI (Beta)"
|
||||||
},
|
},
|
||||||
"uiWelcomeMessage": {
|
"uiWelcomeMessage": {
|
||||||
"message": "Zdaj uporabljate novi MetaMask uporabniški vmesnik. Razglejte se, preizkusite nove funkcije, kot so pošiljanje žetonov, in nas obvestite, če imate kakšne težave."
|
"message": "Zdaj uporabljate novi MetaMask uporabniški vmesnik. Razglejte se, preizkusite nove funkcije, kot so pošiljanje žetonov, in nas obvestite, če imate kakšne težave."
|
||||||
|
@ -14,9 +14,15 @@
|
|||||||
"address": {
|
"address": {
|
||||||
"message": "地址"
|
"message": "地址"
|
||||||
},
|
},
|
||||||
|
"addCustomToken": {
|
||||||
|
"message": "添加自定义代币"
|
||||||
|
},
|
||||||
"addToken": {
|
"addToken": {
|
||||||
"message": "添加代币"
|
"message": "添加代币"
|
||||||
},
|
},
|
||||||
|
"addTokens": {
|
||||||
|
"message": "添加代币"
|
||||||
|
},
|
||||||
"amount": {
|
"amount": {
|
||||||
"message": "数量"
|
"message": "数量"
|
||||||
},
|
},
|
||||||
@ -31,9 +37,15 @@
|
|||||||
"message": "MetaMask",
|
"message": "MetaMask",
|
||||||
"description": "The name of the application"
|
"description": "The name of the application"
|
||||||
},
|
},
|
||||||
|
"approved": {
|
||||||
|
"message": "批准"
|
||||||
|
},
|
||||||
"attemptingConnect": {
|
"attemptingConnect": {
|
||||||
"message": "正在尝试连接区块链。"
|
"message": "正在尝试连接区块链。"
|
||||||
},
|
},
|
||||||
|
"attributions": {
|
||||||
|
"message": "来源"
|
||||||
|
},
|
||||||
"available": {
|
"available": {
|
||||||
"message": "可用"
|
"message": "可用"
|
||||||
},
|
},
|
||||||
@ -43,6 +55,9 @@
|
|||||||
"balance": {
|
"balance": {
|
||||||
"message": "余额:"
|
"message": "余额:"
|
||||||
},
|
},
|
||||||
|
"balances": {
|
||||||
|
"message": "代币余额"
|
||||||
|
},
|
||||||
"balanceIsInsufficientGas": {
|
"balanceIsInsufficientGas": {
|
||||||
"message": "当前余额不足以支付 Gas"
|
"message": "当前余额不足以支付 Gas"
|
||||||
},
|
},
|
||||||
@ -53,9 +68,15 @@
|
|||||||
"message": "必须大于等于 $1 并且小于等于 $2 。",
|
"message": "必须大于等于 $1 并且小于等于 $2 。",
|
||||||
"description": "helper for inputting hex as decimal input"
|
"description": "helper for inputting hex as decimal input"
|
||||||
},
|
},
|
||||||
|
"blockiesIdenticon": {
|
||||||
|
"message": "使用区块Identicon"
|
||||||
|
},
|
||||||
"borrowDharma": {
|
"borrowDharma": {
|
||||||
"message": "Borrow With Dharma (Beta)"
|
"message": "Borrow With Dharma (Beta)"
|
||||||
},
|
},
|
||||||
|
"builtInCalifornia": {
|
||||||
|
"message": "MetaMask在加利福尼亚设计和制造。"
|
||||||
|
},
|
||||||
"buy": {
|
"buy": {
|
||||||
"message": "购买"
|
"message": "购买"
|
||||||
},
|
},
|
||||||
@ -65,15 +86,27 @@
|
|||||||
"buyCoinbaseExplainer": {
|
"buyCoinbaseExplainer": {
|
||||||
"message": "Coinbase 是世界上最流行的买卖比特币,以太币和莱特币的交易所。"
|
"message": "Coinbase 是世界上最流行的买卖比特币,以太币和莱特币的交易所。"
|
||||||
},
|
},
|
||||||
|
"ok": {
|
||||||
|
"message": "确认"
|
||||||
|
},
|
||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "取消"
|
"message": "取消"
|
||||||
},
|
},
|
||||||
|
"classicInterface": {
|
||||||
|
"message": "使用经典接口"
|
||||||
|
},
|
||||||
"clickCopy": {
|
"clickCopy": {
|
||||||
"message": "点击复制"
|
"message": "点击复制"
|
||||||
},
|
},
|
||||||
|
"close": {
|
||||||
|
"message": "关闭"
|
||||||
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"message": "确认"
|
"message": "确认"
|
||||||
},
|
},
|
||||||
|
"confirmed": {
|
||||||
|
"message": "确认"
|
||||||
|
},
|
||||||
"confirmContract": {
|
"confirmContract": {
|
||||||
"message": "确认合约"
|
"message": "确认合约"
|
||||||
},
|
},
|
||||||
@ -83,6 +116,9 @@
|
|||||||
"confirmTransaction": {
|
"confirmTransaction": {
|
||||||
"message": "确认交易"
|
"message": "确认交易"
|
||||||
},
|
},
|
||||||
|
"continue": {
|
||||||
|
"message": "继续"
|
||||||
|
},
|
||||||
"continueToCoinbase": {
|
"continueToCoinbase": {
|
||||||
"message": "继续访问 Coinbase"
|
"message": "继续访问 Coinbase"
|
||||||
},
|
},
|
||||||
@ -99,7 +135,10 @@
|
|||||||
"message": "已复制到剪贴板"
|
"message": "已复制到剪贴板"
|
||||||
},
|
},
|
||||||
"copiedExclamation": {
|
"copiedExclamation": {
|
||||||
"message": "已复制!"
|
"message": "已复制"
|
||||||
|
},
|
||||||
|
"copiedSafe": {
|
||||||
|
"message": "我已将它复制保存到某个安全的地方"
|
||||||
},
|
},
|
||||||
"copy": {
|
"copy": {
|
||||||
"message": "复制"
|
"message": "复制"
|
||||||
@ -126,15 +165,30 @@
|
|||||||
"message": "加密",
|
"message": "加密",
|
||||||
"description": "Exchange type (cryptocurrencies)"
|
"description": "Exchange type (cryptocurrencies)"
|
||||||
},
|
},
|
||||||
|
"currentConversion": {
|
||||||
|
"message": "当前汇率"
|
||||||
|
},
|
||||||
|
"currentNetwork": {
|
||||||
|
"message": "当前网络"
|
||||||
|
},
|
||||||
"customGas": {
|
"customGas": {
|
||||||
"message": "自定义 Gas"
|
"message": "自定义 Gas"
|
||||||
},
|
},
|
||||||
|
"customToken": {
|
||||||
|
"message": "自定义代币"
|
||||||
|
},
|
||||||
"customize": {
|
"customize": {
|
||||||
"message": "自定义"
|
"message": "自定义"
|
||||||
},
|
},
|
||||||
"customRPC": {
|
"customRPC": {
|
||||||
"message": "自定义 RPC"
|
"message": "自定义 RPC"
|
||||||
},
|
},
|
||||||
|
"decimalsMustZerotoTen": {
|
||||||
|
"message": "小数位最小为0并且不超过36位."
|
||||||
|
},
|
||||||
|
"decimal": {
|
||||||
|
"message": "精确小数点"
|
||||||
|
},
|
||||||
"defaultNetwork": {
|
"defaultNetwork": {
|
||||||
"message": "默认以太坊交易网络为主网。"
|
"message": "默认以太坊交易网络为主网。"
|
||||||
},
|
},
|
||||||
@ -184,18 +238,39 @@
|
|||||||
"done": {
|
"done": {
|
||||||
"message": "完成"
|
"message": "完成"
|
||||||
},
|
},
|
||||||
|
"downloadStateLogs": {
|
||||||
|
"message": "下载日志"
|
||||||
|
},
|
||||||
|
"dropped": {
|
||||||
|
"message": "丢弃"
|
||||||
|
},
|
||||||
"edit": {
|
"edit": {
|
||||||
"message": "编辑"
|
"message": "编辑"
|
||||||
},
|
},
|
||||||
"editAccountName": {
|
"editAccountName": {
|
||||||
"message": "编辑账户名称"
|
"message": "编辑账户名称"
|
||||||
},
|
},
|
||||||
|
"emailUs": {
|
||||||
|
"message": "联系我们"
|
||||||
|
},
|
||||||
"encryptNewDen": {
|
"encryptNewDen": {
|
||||||
"message": "加密你的新 DEN"
|
"message": "加密你的新 DEN"
|
||||||
},
|
},
|
||||||
"enterPassword": {
|
"enterPassword": {
|
||||||
"message": "请输入密码"
|
"message": "请输入密码"
|
||||||
},
|
},
|
||||||
|
"enterPasswordConfirm": {
|
||||||
|
"message": "请输入密码以确认"
|
||||||
|
},
|
||||||
|
"enterPasswordContinue": {
|
||||||
|
"message": "请输入密码以继续"
|
||||||
|
},
|
||||||
|
"passwordNotLongEnough": {
|
||||||
|
"message": "密码长度不足"
|
||||||
|
},
|
||||||
|
"passwordsDontMatch": {
|
||||||
|
"message": "密码不匹配"
|
||||||
|
},
|
||||||
"etherscanView": {
|
"etherscanView": {
|
||||||
"message": "在 Etherscan 上查看账户"
|
"message": "在 Etherscan 上查看账户"
|
||||||
},
|
},
|
||||||
@ -219,9 +294,15 @@
|
|||||||
"message": "文件导入失败? 点击这里!",
|
"message": "文件导入失败? 点击这里!",
|
||||||
"description": "Helps user import their account from a JSON file"
|
"description": "Helps user import their account from a JSON file"
|
||||||
},
|
},
|
||||||
|
"followTwitter": {
|
||||||
|
"message": "关注我们的Twitter"
|
||||||
|
},
|
||||||
"from": {
|
"from": {
|
||||||
"message": "来自"
|
"message": "来自"
|
||||||
},
|
},
|
||||||
|
"fromToSame": {
|
||||||
|
"message": "发送和接受地址不能相同"
|
||||||
|
},
|
||||||
"fromShapeShift": {
|
"fromShapeShift": {
|
||||||
"message": "来自 ShapeShift"
|
"message": "来自 ShapeShift"
|
||||||
},
|
},
|
||||||
@ -244,6 +325,9 @@
|
|||||||
"gasLimitTooLow": {
|
"gasLimitTooLow": {
|
||||||
"message": "Gas Limit 至少要 21000"
|
"message": "Gas Limit 至少要 21000"
|
||||||
},
|
},
|
||||||
|
"generatingSeed": {
|
||||||
|
"message": "生成密钥中..."
|
||||||
|
},
|
||||||
"gasPrice": {
|
"gasPrice": {
|
||||||
"message": "Gas Price (GWEI)"
|
"message": "Gas Price (GWEI)"
|
||||||
},
|
},
|
||||||
@ -253,6 +337,9 @@
|
|||||||
"gasPriceRequired": {
|
"gasPriceRequired": {
|
||||||
"message": "Gas Price 必填"
|
"message": "Gas Price 必填"
|
||||||
},
|
},
|
||||||
|
"generatingTransaction": {
|
||||||
|
"message": "生成 交易"
|
||||||
|
},
|
||||||
"getEther": {
|
"getEther": {
|
||||||
"message": "获取 Ether"
|
"message": "获取 Ether"
|
||||||
},
|
},
|
||||||
@ -268,6 +355,9 @@
|
|||||||
"message": "这里",
|
"message": "这里",
|
||||||
"description": "as in -click here- for more information (goes with troubleTokenBalances)"
|
"description": "as in -click here- for more information (goes with troubleTokenBalances)"
|
||||||
},
|
},
|
||||||
|
"hereList": {
|
||||||
|
"message": "Here's a list!!!!"
|
||||||
|
},
|
||||||
"hide": {
|
"hide": {
|
||||||
"message": "隐藏"
|
"message": "隐藏"
|
||||||
},
|
},
|
||||||
@ -280,6 +370,9 @@
|
|||||||
"howToDeposit": {
|
"howToDeposit": {
|
||||||
"message": "你想怎样转入 Ether?"
|
"message": "你想怎样转入 Ether?"
|
||||||
},
|
},
|
||||||
|
"holdEther": {
|
||||||
|
"message": "它允许你保存ether和代币,并作为你使用Dapp的桥梁."
|
||||||
|
},
|
||||||
"import": {
|
"import": {
|
||||||
"message": "导入",
|
"message": "导入",
|
||||||
"description": "Button to import an account from a selected file"
|
"description": "Button to import an account from a selected file"
|
||||||
@ -287,6 +380,9 @@
|
|||||||
"importAccount": {
|
"importAccount": {
|
||||||
"message": "导入账户"
|
"message": "导入账户"
|
||||||
},
|
},
|
||||||
|
"importAccountMsg": {
|
||||||
|
"message":" Imported accounts will not be associated with your originally created MetaMask account seedphrase. Learn more about imported accounts "
|
||||||
|
},
|
||||||
"importAnAccount": {
|
"importAnAccount": {
|
||||||
"message": "导入一个账户"
|
"message": "导入一个账户"
|
||||||
},
|
},
|
||||||
@ -294,46 +390,82 @@
|
|||||||
"message": "导入存在的 DEN"
|
"message": "导入存在的 DEN"
|
||||||
},
|
},
|
||||||
"imported": {
|
"imported": {
|
||||||
"message": "已导入私钥",
|
"message": "已导入",
|
||||||
"description": "status showing that an account has been fully loaded into the keyring"
|
"description": "status showing that an account has been fully loaded into the keyring"
|
||||||
},
|
},
|
||||||
"infoHelp": {
|
"infoHelp": {
|
||||||
"message": "信息 & 帮助"
|
"message": "信息 & 帮助"
|
||||||
},
|
},
|
||||||
|
"insufficientFunds": {
|
||||||
|
"message": "余额不足."
|
||||||
|
},
|
||||||
|
"insufficientTokens": {
|
||||||
|
"message": "代币余额不足."
|
||||||
|
},
|
||||||
"invalidAddress": {
|
"invalidAddress": {
|
||||||
"message": "错误的地址"
|
"message": "无效地址"
|
||||||
|
},
|
||||||
|
"invalidAddressRecipient": {
|
||||||
|
"message": "收款地址不合法"
|
||||||
},
|
},
|
||||||
"invalidGasParams": {
|
"invalidGasParams": {
|
||||||
"message": "错误的 Gas 参数"
|
"message": "无效 Gas 参数"
|
||||||
},
|
},
|
||||||
"invalidInput": {
|
"invalidInput": {
|
||||||
"message": "错误的输入。"
|
"message": "无效输入."
|
||||||
},
|
},
|
||||||
"invalidRequest": {
|
"invalidRequest": {
|
||||||
"message": "无效请求"
|
"message": "无效请求"
|
||||||
},
|
},
|
||||||
|
"invalidRPC": {
|
||||||
|
"message": "无效 RPC URI"
|
||||||
|
},
|
||||||
|
"jsonFail": {
|
||||||
|
"message": "Something went wrong. Please make sure your JSON file is properly formatted."
|
||||||
|
},
|
||||||
"jsonFile": {
|
"jsonFile": {
|
||||||
"message": "JSON 文件",
|
"message": "JSON 文件",
|
||||||
"description": "format for importing an account"
|
"description": "format for importing an account"
|
||||||
},
|
},
|
||||||
|
"keepTrackTokens": {
|
||||||
|
"message": "Keep track of the tokens you’ve bought with your MetaMask account."
|
||||||
|
},
|
||||||
"kovan": {
|
"kovan": {
|
||||||
"message": "Kovan 测试网络"
|
"message": "Kovan 测试网络"
|
||||||
},
|
},
|
||||||
|
"knowledgeDataBase": {
|
||||||
|
"message": "浏览我们的知识库"
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"message": "最大"
|
||||||
|
},
|
||||||
|
"learnMore": {
|
||||||
|
"message": "查看更多."
|
||||||
|
},
|
||||||
"lessThanMax": {
|
"lessThanMax": {
|
||||||
"message": "必须小于等于 $1.",
|
"message": "必须小于或等于 $1.",
|
||||||
"description": "helper for inputting hex as decimal input"
|
"description": "helper for inputting hex as decimal input"
|
||||||
},
|
},
|
||||||
|
"likeToAddTokens": {
|
||||||
|
"message": "你想添加这些代币吗?"
|
||||||
|
},
|
||||||
|
"links": {
|
||||||
|
"message": "链接"
|
||||||
|
},
|
||||||
"limit": {
|
"limit": {
|
||||||
"message": "限定"
|
"message": "限制"
|
||||||
},
|
},
|
||||||
"loading": {
|
"loading": {
|
||||||
"message": "加载..."
|
"message": "加载中..."
|
||||||
},
|
},
|
||||||
"loadingTokens": {
|
"loadingTokens": {
|
||||||
"message": "加载代币..."
|
"message": "加载代币中..."
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"message": "本地主机 8545"
|
"message": "Localhost 8545"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"message": "登录"
|
||||||
},
|
},
|
||||||
"logout": {
|
"logout": {
|
||||||
"message": "登出"
|
"message": "登出"
|
||||||
@ -341,17 +473,29 @@
|
|||||||
"loose": {
|
"loose": {
|
||||||
"message": "疏松"
|
"message": "疏松"
|
||||||
},
|
},
|
||||||
|
"loweCaseWords": {
|
||||||
|
"message": "助记词只有小写字符"
|
||||||
|
},
|
||||||
"mainnet": {
|
"mainnet": {
|
||||||
"message": "以太坊主网络"
|
"message": "以太坊主网络"
|
||||||
},
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"message": "消息"
|
"message": "消息"
|
||||||
},
|
},
|
||||||
|
"metamaskDescription": {
|
||||||
|
"message": "MetaMask is a secure identity vault for Ethereum."
|
||||||
|
},
|
||||||
|
"metamaskSeedWords": {
|
||||||
|
"message": "MetaMask 助记词"
|
||||||
|
},
|
||||||
"min": {
|
"min": {
|
||||||
"message": "最小"
|
"message": "最小"
|
||||||
},
|
},
|
||||||
"myAccounts": {
|
"myAccounts": {
|
||||||
"message": "我的账户"
|
"message": "My Accounts"
|
||||||
|
},
|
||||||
|
"mustSelectOne": {
|
||||||
|
"message": "至少选择一种代币."
|
||||||
},
|
},
|
||||||
"needEtherInWallet": {
|
"needEtherInWallet": {
|
||||||
"message": "使用 MetaMask 与 DAPP 交互,需要你的钱包里有 Ether。"
|
"message": "使用 MetaMask 与 DAPP 交互,需要你的钱包里有 Ether。"
|
||||||
@ -361,9 +505,12 @@
|
|||||||
"description": "User is important an account and needs to add a file to continue"
|
"description": "User is important an account and needs to add a file to continue"
|
||||||
},
|
},
|
||||||
"needImportPassword": {
|
"needImportPassword": {
|
||||||
"message": "必须为已选择的文件输入密码。",
|
"message": "必须为已选择的文件输入密码。",
|
||||||
"description": "Password and file needed to import an account"
|
"description": "Password and file needed to import an account"
|
||||||
},
|
},
|
||||||
|
"negativeETH": {
|
||||||
|
"message": "Can not send negative amounts of ETH."
|
||||||
|
},
|
||||||
"networks": {
|
"networks": {
|
||||||
"message": "网络"
|
"message": "网络"
|
||||||
},
|
},
|
||||||
@ -383,8 +530,11 @@
|
|||||||
"newRecipient": {
|
"newRecipient": {
|
||||||
"message": "新收款人"
|
"message": "新收款人"
|
||||||
},
|
},
|
||||||
|
"newRPC": {
|
||||||
|
"message": "新 RPC URL"
|
||||||
|
},
|
||||||
"next": {
|
"next": {
|
||||||
"message": "下一个"
|
"message": "下一步"
|
||||||
},
|
},
|
||||||
"noAddressForName": {
|
"noAddressForName": {
|
||||||
"message": "此 ENS 名字还没有指定地址。"
|
"message": "此 ENS 名字还没有指定地址。"
|
||||||
@ -405,12 +555,18 @@
|
|||||||
"message": "旧版界面"
|
"message": "旧版界面"
|
||||||
},
|
},
|
||||||
"oldUIMessage": {
|
"oldUIMessage": {
|
||||||
"message": "你已经切换到旧版界面。 你可以通过右上方下拉菜单中的选项切换回新的用户界面。"
|
"message": "你已经切换到旧版界面。 你可以通过右上方下拉菜单中的选项切换回新的用户界面。"
|
||||||
},
|
},
|
||||||
"or": {
|
"or": {
|
||||||
"message": "或",
|
"message": "或",
|
||||||
"description": "choice between creating or importing a new account"
|
"description": "choice between creating or importing a new account"
|
||||||
},
|
},
|
||||||
|
"password": {
|
||||||
|
"message": "密码"
|
||||||
|
},
|
||||||
|
"passwordCorrect": {
|
||||||
|
"message": "Please make sure your password is correct."
|
||||||
|
},
|
||||||
"passwordMismatch": {
|
"passwordMismatch": {
|
||||||
"message": "密码不匹配",
|
"message": "密码不匹配",
|
||||||
"description": "in password creation process, the two new password fields did not match"
|
"description": "in password creation process, the two new password fields did not match"
|
||||||
@ -426,15 +582,24 @@
|
|||||||
"pasteSeed": {
|
"pasteSeed": {
|
||||||
"message": "请粘贴你的助记词!"
|
"message": "请粘贴你的助记词!"
|
||||||
},
|
},
|
||||||
|
"personalAddressDetected": {
|
||||||
|
"message": "检测到个人地址。请输入代币合约地址。"
|
||||||
|
},
|
||||||
"pleaseReviewTransaction": {
|
"pleaseReviewTransaction": {
|
||||||
"message": "请检查你的交易。"
|
"message": "请检查你的交易。"
|
||||||
},
|
},
|
||||||
|
"popularTokens": {
|
||||||
|
"message": "常用代币"
|
||||||
|
},
|
||||||
|
"privacyMsg": {
|
||||||
|
"message": "隐私政策"
|
||||||
|
},
|
||||||
"privateKey": {
|
"privateKey": {
|
||||||
"message": "私钥",
|
"message": "私钥",
|
||||||
"description": "select this type of file to use to import an account"
|
"description": "select this type of file to use to import an account"
|
||||||
},
|
},
|
||||||
"privateKeyWarning": {
|
"privateKeyWarning": {
|
||||||
"message": "注意:永远不要公开这个私钥。任何拥有你的私钥的人都可以窃取你帐户中的任何资产。"
|
"message": "注意:永远不要公开这个私钥。任何拥有你的私钥的人都可以窃取你帐户中的任何资产。"
|
||||||
},
|
},
|
||||||
"privateNetwork": {
|
"privateNetwork": {
|
||||||
"message": "私有网络"
|
"message": "私有网络"
|
||||||
@ -443,11 +608,14 @@
|
|||||||
"message": "显示二维码"
|
"message": "显示二维码"
|
||||||
},
|
},
|
||||||
"readdToken": {
|
"readdToken": {
|
||||||
"message": "之后你还可以通过帐户选项菜单中的“添加代币”来添加此代币。"
|
"message": "之后你还可以通过帐户选项菜单中的“添加代币”来添加此代币。"
|
||||||
},
|
},
|
||||||
"readMore": {
|
"readMore": {
|
||||||
"message": "了解更多。"
|
"message": "了解更多。"
|
||||||
},
|
},
|
||||||
|
"readMore2": {
|
||||||
|
"message": "了解更多。"
|
||||||
|
},
|
||||||
"receive": {
|
"receive": {
|
||||||
"message": "接收"
|
"message": "接收"
|
||||||
},
|
},
|
||||||
@ -460,12 +628,39 @@
|
|||||||
"rejected": {
|
"rejected": {
|
||||||
"message": "拒绝"
|
"message": "拒绝"
|
||||||
},
|
},
|
||||||
|
"resetAccount": {
|
||||||
|
"message": "重设账户"
|
||||||
|
},
|
||||||
|
"restoreFromSeed": {
|
||||||
|
"message": "从助记词还原"
|
||||||
|
},
|
||||||
|
"restoreVault": {
|
||||||
|
"message": "还原保险柜"
|
||||||
|
},
|
||||||
"required": {
|
"required": {
|
||||||
"message": "必填"
|
"message": "必填"
|
||||||
},
|
},
|
||||||
"retryWithMoreGas": {
|
"retryWithMoreGas": {
|
||||||
"message": "使用更高的 Gas Price 重试"
|
"message": "使用更高的 Gas Price 重试"
|
||||||
},
|
},
|
||||||
|
"walletSeed": {
|
||||||
|
"message": "钱包助记词"
|
||||||
|
},
|
||||||
|
"revealSeedWords": {
|
||||||
|
"message": "显示助记词"
|
||||||
|
},
|
||||||
|
"revealSeedWordsTitle": {
|
||||||
|
"message": "助记词"
|
||||||
|
},
|
||||||
|
"revealSeedWordsDescription": {
|
||||||
|
"message": "如果您更换浏览器或计算机,则需要使用此助记词访问您的帐户。请将它们保存在安全秘密的地方。"
|
||||||
|
},
|
||||||
|
"revealSeedWordsWarningTitle": {
|
||||||
|
"message": "不要对任何人展示助记词!"
|
||||||
|
},
|
||||||
|
"revealSeedWordsWarning": {
|
||||||
|
"message": "助记词可以用来窃取您的所有帐户."
|
||||||
|
},
|
||||||
"revert": {
|
"revert": {
|
||||||
"message": "还原"
|
"message": "还原"
|
||||||
},
|
},
|
||||||
@ -475,6 +670,24 @@
|
|||||||
"ropsten": {
|
"ropsten": {
|
||||||
"message": "Ropsten 测试网络"
|
"message": "Ropsten 测试网络"
|
||||||
},
|
},
|
||||||
|
"currentRpc": {
|
||||||
|
"message": "当前 RPC"
|
||||||
|
},
|
||||||
|
"connectingToMainnet": {
|
||||||
|
"message": "正在连接到以太坊主网"
|
||||||
|
},
|
||||||
|
"connectingToRopsten": {
|
||||||
|
"message": "正在连接到Ropsten测试网络"
|
||||||
|
},
|
||||||
|
"connectingToKovan": {
|
||||||
|
"message": "正在连接到Kovan测试网络"
|
||||||
|
},
|
||||||
|
"connectingToRinkeby": {
|
||||||
|
"message": "正在连接到Rinkeby测试网络"
|
||||||
|
},
|
||||||
|
"connectingToUnknown": {
|
||||||
|
"message": "正在连接到未知网络"
|
||||||
|
},
|
||||||
"sampleAccountName": {
|
"sampleAccountName": {
|
||||||
"message": "例如:我的账户",
|
"message": "例如:我的账户",
|
||||||
"description": "Help user understand concept of adding a human-readable name to their account"
|
"description": "Help user understand concept of adding a human-readable name to their account"
|
||||||
@ -482,25 +695,70 @@
|
|||||||
"save": {
|
"save": {
|
||||||
"message": "保存"
|
"message": "保存"
|
||||||
},
|
},
|
||||||
|
"reprice_title": {
|
||||||
|
"message": "重新出价交易"
|
||||||
|
},
|
||||||
|
"reprice_subtitle": {
|
||||||
|
"message": "提高 GAS 价格尝试覆盖并加速交易"
|
||||||
|
},
|
||||||
|
"saveAsCsvFile": {
|
||||||
|
"message": "另存为CSV文件"
|
||||||
|
},
|
||||||
"saveAsFile": {
|
"saveAsFile": {
|
||||||
"message": "保存文件",
|
"message": "保存文件",
|
||||||
"description": "Account export process"
|
"description": "Account export process"
|
||||||
},
|
},
|
||||||
|
"saveSeedAsFile": {
|
||||||
|
"message": "保存助记词为文件"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"message": "搜索"
|
||||||
|
},
|
||||||
|
"secretPhrase": {
|
||||||
|
"message": "输入12位助记词以恢复金库."
|
||||||
|
},
|
||||||
|
"newPassword8Chars": {
|
||||||
|
"message": "新密码(至少8位)"
|
||||||
|
},
|
||||||
|
"seedPhraseReq": {
|
||||||
|
"message": "助记词为12个单词"
|
||||||
|
},
|
||||||
|
"select": {
|
||||||
|
"message": "选择"
|
||||||
|
},
|
||||||
|
"selectCurrency": {
|
||||||
|
"message": "选择货币"
|
||||||
|
},
|
||||||
"selectService": {
|
"selectService": {
|
||||||
"message": "选择服务"
|
"message": "选择服务"
|
||||||
},
|
},
|
||||||
|
"selectType": {
|
||||||
|
"message": "选择类型"
|
||||||
|
},
|
||||||
"send": {
|
"send": {
|
||||||
"message": "发送"
|
"message": "发送"
|
||||||
},
|
},
|
||||||
|
"sendETH": {
|
||||||
|
"message": "发送 ETH"
|
||||||
|
},
|
||||||
"sendTokens": {
|
"sendTokens": {
|
||||||
"message": "发送代币"
|
"message": "发送 代币"
|
||||||
|
},
|
||||||
|
"onlySendToEtherAddress": {
|
||||||
|
"message": "只发送 ETH 给一个以太坊地址"
|
||||||
|
},
|
||||||
|
"searchTokens": {
|
||||||
|
"message": "搜索代币"
|
||||||
},
|
},
|
||||||
"sendTokensAnywhere": {
|
"sendTokensAnywhere": {
|
||||||
"message": "发送代币给拥有以太坊账户的任何人"
|
"message": "将代币发送给拥有以太坊地址的任何人"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"message": "设置"
|
"message": "设置"
|
||||||
},
|
},
|
||||||
|
"info": {
|
||||||
|
"message": "信息"
|
||||||
|
},
|
||||||
"shapeshiftBuy": {
|
"shapeshiftBuy": {
|
||||||
"message": "使用 Shapeshift 购买"
|
"message": "使用 Shapeshift 购买"
|
||||||
},
|
},
|
||||||
@ -513,6 +771,9 @@
|
|||||||
"sign": {
|
"sign": {
|
||||||
"message": "签名"
|
"message": "签名"
|
||||||
},
|
},
|
||||||
|
"signed": {
|
||||||
|
"message": "已签名"
|
||||||
|
},
|
||||||
"signMessage": {
|
"signMessage": {
|
||||||
"message": "签署消息"
|
"message": "签署消息"
|
||||||
},
|
},
|
||||||
@ -525,15 +786,39 @@
|
|||||||
"sigRequested": {
|
"sigRequested": {
|
||||||
"message": "签名已请求"
|
"message": "签名已请求"
|
||||||
},
|
},
|
||||||
|
"spaceBetween": {
|
||||||
|
"message": "单词之间只能有一个空格"
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"message": "状态"
|
"message": "状态"
|
||||||
},
|
},
|
||||||
|
"stateLogs": {
|
||||||
|
"message": "状态日志"
|
||||||
|
},
|
||||||
|
"stateLogsDescription": {
|
||||||
|
"message": "状态日志包含您的账户地址和已发送的交易。"
|
||||||
|
},
|
||||||
|
"stateLogError": {
|
||||||
|
"message": "检索状态日志时出错。"
|
||||||
|
},
|
||||||
"submit": {
|
"submit": {
|
||||||
"message": "提交"
|
"message": "提交"
|
||||||
},
|
},
|
||||||
|
"submitted": {
|
||||||
|
"message": "已提交"
|
||||||
|
},
|
||||||
|
"supportCenter": {
|
||||||
|
"message": "访问我们的支持中心"
|
||||||
|
},
|
||||||
|
"symbolBetweenZeroTen": {
|
||||||
|
"message": "符号应该有0-10个字符."
|
||||||
|
},
|
||||||
"takesTooLong": {
|
"takesTooLong": {
|
||||||
"message": "花费太长时间?"
|
"message": "花费太长时间?"
|
||||||
},
|
},
|
||||||
|
"terms": {
|
||||||
|
"message": "使用条款"
|
||||||
|
},
|
||||||
"testFaucet": {
|
"testFaucet": {
|
||||||
"message": "测试水管"
|
"message": "测试水管"
|
||||||
},
|
},
|
||||||
@ -544,33 +829,60 @@
|
|||||||
"message": "$1 ETH 通过 ShapeShift",
|
"message": "$1 ETH 通过 ShapeShift",
|
||||||
"description": "system will fill in deposit type in start of message"
|
"description": "system will fill in deposit type in start of message"
|
||||||
},
|
},
|
||||||
|
"tokenAddress": {
|
||||||
|
"message": "代币地址"
|
||||||
|
},
|
||||||
|
"tokenAlreadyAdded": {
|
||||||
|
"message": "代币已经被添加."
|
||||||
|
},
|
||||||
"tokenBalance": {
|
"tokenBalance": {
|
||||||
"message": "代币余额:"
|
"message": "代币余额:"
|
||||||
},
|
},
|
||||||
|
"tokenSelection": {
|
||||||
|
"message": "搜索代币或从我们的常用代币列表中进行选择"
|
||||||
|
},
|
||||||
|
"tokenSymbol": {
|
||||||
|
"message": "代币符号"
|
||||||
|
},
|
||||||
|
"tokenWarning1": {
|
||||||
|
"message": "Keep track of the tokens you’ve bought with your MetaMask account. If you bought tokens using a different account, those tokens will not appear here."
|
||||||
|
},
|
||||||
"total": {
|
"total": {
|
||||||
"message": "总量"
|
"message": "总量"
|
||||||
},
|
},
|
||||||
|
"transactions": {
|
||||||
|
"message": "交易"
|
||||||
|
},
|
||||||
|
"transactionError": {
|
||||||
|
"message": "交易出错. 合约代码执行异常."
|
||||||
|
},
|
||||||
"transactionMemo": {
|
"transactionMemo": {
|
||||||
"message": "交易备注 (可选)"
|
"message": "交易备注(可选)"
|
||||||
},
|
},
|
||||||
"transactionNumber": {
|
"transactionNumber": {
|
||||||
"message": "交易号"
|
"message": "交易 number"
|
||||||
},
|
},
|
||||||
"transfers": {
|
"transfers": {
|
||||||
"message": "Transfers"
|
"message": "交易"
|
||||||
},
|
},
|
||||||
"troubleTokenBalances": {
|
"troubleTokenBalances": {
|
||||||
"message": "无法加载代币余额。你可以再这里查看 ",
|
"message": "我们无法加载您的代币余额。你可以查看它们",
|
||||||
"description": "Followed by a link (here) to view token balances"
|
"description": "Followed by a link (here) to view token balances"
|
||||||
},
|
},
|
||||||
|
"twelveWords": {
|
||||||
|
"message": "这12个单词是恢复MetaMask帐户的唯一方法。.\n将它们存放在安全和秘密的地方。."
|
||||||
|
},
|
||||||
"typePassword": {
|
"typePassword": {
|
||||||
"message": "请输入密码"
|
"message": "输入你的密码"
|
||||||
},
|
},
|
||||||
"uiWelcome": {
|
"uiWelcome": {
|
||||||
"message": "欢迎使用新版界面 (Beta)"
|
"message": "欢迎使用新版界面 (Beta)"
|
||||||
},
|
},
|
||||||
"uiWelcomeMessage": {
|
"uiWelcomeMessage": {
|
||||||
"message": "你现在正在使用新的 Metamask 界面。 尝试发送代币等新功能,有任何问题请告知我们。"
|
"message": "你现在正在使用新的 Metamask 界面。 尝试发送代币等新功能,有任何问题请告知我们。"
|
||||||
|
},
|
||||||
|
"unapproved": {
|
||||||
|
"message": "未批准"
|
||||||
},
|
},
|
||||||
"unavailable": {
|
"unavailable": {
|
||||||
"message": "不可用"
|
"message": "不可用"
|
||||||
@ -582,7 +894,10 @@
|
|||||||
"message": "未知私有网络"
|
"message": "未知私有网络"
|
||||||
},
|
},
|
||||||
"unknownNetworkId": {
|
"unknownNetworkId": {
|
||||||
"message": "未知网络 ID"
|
"message": "未知网络ID"
|
||||||
|
},
|
||||||
|
"uriErrorMsg": {
|
||||||
|
"message": "URIs require the appropriate HTTP/HTTPS prefix."
|
||||||
},
|
},
|
||||||
"usaOnly": {
|
"usaOnly": {
|
||||||
"message": "只限于美国",
|
"message": "只限于美国",
|
||||||
@ -591,12 +906,27 @@
|
|||||||
"usedByClients": {
|
"usedByClients": {
|
||||||
"message": "可用于各种不同的客户端"
|
"message": "可用于各种不同的客户端"
|
||||||
},
|
},
|
||||||
|
"useOldUI": {
|
||||||
|
"message": "使用旧版 UI"
|
||||||
|
},
|
||||||
|
"validFileImport": {
|
||||||
|
"message": "您必须选择一个有效的文件进行导入."
|
||||||
|
},
|
||||||
|
"vaultCreated": {
|
||||||
|
"message": "已创建保险库"
|
||||||
|
},
|
||||||
"viewAccount": {
|
"viewAccount": {
|
||||||
"message": "查看账户"
|
"message": "查看账户"
|
||||||
},
|
},
|
||||||
|
"visitWebSite": {
|
||||||
|
"message": "访问我们的网站"
|
||||||
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
"message": "警告"
|
"message": "警告"
|
||||||
},
|
},
|
||||||
|
"welcomeBeta": {
|
||||||
|
"message": "欢迎使用 MetaMask 测试版"
|
||||||
|
},
|
||||||
"whatsThis": {
|
"whatsThis": {
|
||||||
"message": "这是什么?"
|
"message": "这是什么?"
|
||||||
},
|
},
|
||||||
@ -605,5 +935,8 @@
|
|||||||
},
|
},
|
||||||
"youSign": {
|
"youSign": {
|
||||||
"message": "正在签名"
|
"message": "正在签名"
|
||||||
|
},
|
||||||
|
"yourPrivateSeedPhrase": {
|
||||||
|
"message": "你的私有助记词"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
17
app/images/check-icon.svg
Normal file
17
app/images/check-icon.svg
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: sketchtool 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>76BCDB09-52B0-41CB-908F-12F9087A2F1B</title>
|
||||||
|
<desc>Created with sketchtool.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Confirm-TX-screen" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="confirmed-alert" transform="translate(-144.000000, -53.000000)" stroke="#61BA00" stroke-width="4">
|
||||||
|
<g id="Group-17-Copy" transform="translate(22.000000, 20.000000)">
|
||||||
|
<g id="check-icon" transform="translate(124.000000, 35.000000)">
|
||||||
|
<circle id="Oval-5" cx="48" cy="48" r="48"></circle>
|
||||||
|
<polyline id="Path-3" stroke-linecap="round" points="29.76 52.8 41.0023819 64.32 71.04 34.56"></polyline>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
14
app/images/search.svg
Normal file
14
app/images/search.svg
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="17px" height="17px" viewBox="0 0 17 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>search</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Add-Tokens" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Metamascara---add-from-token-list-Copy-3" transform="translate(-345.000000, -350.000000)" fill="#9B9B9B" fill-rule="nonzero">
|
||||||
|
<g id="search" transform="translate(345.000000, 350.000000)">
|
||||||
|
<path d="M2.01875,6.90625 C2.01875,4.25 4.25,2.01875 6.90625,2.01875 C9.5625,2.01875 11.6875,4.14375 11.6875,6.90625 C11.6875,9.5625 9.5625,11.6875 6.90625,11.6875 C4.14375,11.6875 2.01875,9.5625 2.01875,6.90625 Z M16.575,15.0875 L12.325,10.8375 C13.175,9.66875 13.6,8.2875 13.6,6.8 C13.70625,3.08125 10.625,0 6.90625,0 C3.08125,0 0,3.08125 0,6.90625 C0,10.73125 3.08125,13.8125 6.90625,13.8125 C8.18125,13.8125 9.45625,13.3875 10.4125,12.75 L14.6625,17 L16.575,15.0875 Z" id="Page-1"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
15
app/images/tokensearch.svg
Normal file
15
app/images/tokensearch.svg
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="65px" height="58px" viewBox="0 0 65 58" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: sketchtool 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>7FDB75AD-BD4D-497C-B391-69EEB31A0561</title>
|
||||||
|
<desc>Created with sketchtool.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Add-Tokens" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Add-tokens" transform="translate(-267.000000, -284.000000)" fill="#B8BAC1">
|
||||||
|
<g id="tokensearch" transform="translate(267.000000, 284.000000)">
|
||||||
|
<path d="M28.5322581,2.80645161 C42.4391613,2.80645161 54.1925806,9.22854839 54.2552581,16.8433871 C54.1925806,24.4591613 42.4391613,30.8821935 28.5322581,30.8821935 C14.6253548,30.8821935 2.87193548,24.4600968 2.80925806,16.8443226 C2.87193548,9.22854839 14.6253548,2.80645161 28.5322581,2.80645161 M28.5322581,36.7289677 C15.7432581,36.7289677 4.78125806,31.2975484 3.05154839,24.5012581 C7.70932258,29.9981613 17.2559355,33.6886452 28.5322581,33.6886452 C39.8085806,33.6886452 49.3551935,29.9981613 54.0129677,24.5012581 C52.2832581,31.2975484 41.3212581,36.7289677 28.5322581,36.7289677 M28.5322581,54.2692903 C15.7432581,54.2692903 4.78125806,48.837871 3.05154839,42.0415806 C7.70932258,47.5384839 17.2559355,51.2289677 28.5322581,51.2289677 C33.2237097,51.2289677 37.6083226,50.5844194 41.471871,49.4403226 C42.1379355,49.243871 42.5270968,48.5675161 42.4110968,47.8818065 C42.4045484,47.8453226 42.398,47.8079032 42.3923871,47.7704839 C42.2642258,46.9603548 41.439129,46.458 40.6533226,46.6946774 C37.0180323,47.792 32.8822581,48.4225161 28.5322581,48.4225161 C15.7432581,48.4225161 4.78125806,42.9910968 3.05154839,36.1948065 C7.70932258,41.6917097 17.2559355,45.3821935 28.5322581,45.3821935 C33.3649677,45.3821935 37.8730645,44.6983548 41.8217419,43.4906452 C42.2763871,43.3503226 42.6066129,42.976129 42.7422581,42.5196129 L42.7534839,42.4812581 C43.0752903,41.4082581 42.0733871,40.4063548 41.004129,40.7403226 C37.2846452,41.9040645 33.0225806,42.5757419 28.5322581,42.5757419 C15.7432581,42.5757419 4.78125806,37.1443226 3.05154839,30.3480323 C7.70932258,35.8449355 17.2559355,39.5354194 28.5322581,39.5354194 C39.8085806,39.5354194 49.3551935,35.8449355 54.0129677,30.3480323 L54.0129677,33.5492581 C54.0129677,34.3846452 54.6902581,35.0619355 55.5256452,35.0619355 C56.3610323,35.0619355 57.0383226,34.3902581 57.0392581,33.5558065 C57.0467419,26.4900968 57.0645161,16.9257097 57.0645161,16.905129 C57.0645161,16.8845484 57.0617097,16.8649032 57.0617097,16.8443226 C57.0617097,16.8237419 57.0645161,16.8031613 57.0645161,16.7825806 L57.0598387,16.7825806 C56.9513226,7.36225806 44.4616774,0 28.5322581,0 C12.6028387,0 0.113193548,7.36225806 0.00467741935,16.7825806 L0,16.7825806 C0,16.8031613 0.00280645161,16.8237419 0.00280645161,16.8443226 C0.00280645161,16.8649032 0,16.8845484 0,16.905129 C0,16.9322581 0.00467741935,19.3420645 0.0102903226,22.6293548 L0,22.6293548 C0,22.7154194 0.00841935484,22.7996129 0.0102903226,22.8838065 C0.0140322581,24.5957419 0.0177741935,26.5247097 0.0196451613,28.476129 L0,28.476129 C0,28.650129 0.0130967742,28.8222581 0.0205806452,28.9953226 C0.0243225806,30.828871 0.0280645161,32.6586774 0.0308709677,34.3229032 L0,34.3229032 C0,34.5857742 0.0140322581,34.8467742 0.0318064516,35.1059032 C0.036483871,37.3108387 0.0392903226,39.1406452 0.0411612903,40.1696774 L0,40.1696774 C0,40.4905484 0.0177741935,40.8086129 0.0458387097,41.123871 L0.0495806452,41.2033871 C0.0645483871,41.3699032 0.0935483871,41.5345484 0.116935484,41.700129 C0.130032258,41.7861935 0.137516129,41.8731935 0.152483871,41.9583226 C0.183354839,42.1416774 0.225451613,42.3240968 0.266612903,42.5055806 C0.29,42.6103548 0.308709677,42.7160645 0.334903226,42.8199032 C0.358290323,42.9078387 0.387290323,42.9939032 0.411612903,43.0818387 C2.00006452,48.7134516 8.12841935,53.3160323 16.5777097,55.5705484 C16.6010968,55.5770968 16.6254194,55.5836452 16.6488065,55.5892581 C16.9350645,55.6650323 17.2213226,55.739871 17.5122581,55.8109677 C20.9099355,56.6538387 24.6322258,57.1215806 28.5322581,57.1215806 C32.4322903,57.1215806 36.1545806,56.6538387 39.5522581,55.8109677 C39.8431935,55.739871 40.1294516,55.6650323 40.4157097,55.5892581 C40.4390968,55.5836452 40.4634194,55.5770968 40.4868065,55.5705484 C41.5766452,55.2796129 42.6253226,54.9475161 43.6319032,54.579871 C44.4682258,54.2739677 44.7675806,53.2627097 44.2652258,52.5274194 C44.2437097,52.4956129 44.2212581,52.462871 44.1997419,52.430129 C43.8423871,51.8950323 43.1688387,51.6873548 42.5645161,51.9090645 C38.4998387,53.3955484 33.6624516,54.2692903 28.5322581,54.2692903" id="Fill-1"></path>
|
||||||
|
<path d="M64.3227484,54.3991355 L60.4535871,50.5299742 C61.4526839,49.1566839 61.9522323,47.5345548 61.9522323,45.7880065 C62.1009742,40.5661355 56.8996839,36.4144581 51.4654581,38.2367806 C48.6131677,39.1928452 46.4821355,41.7401677 46.0611677,44.7187484 C45.3530065,49.7460387 49.205329,54.0249419 54.0894903,54.0249419 C55.5872,54.0249419 57.0849097,53.5244581 58.2074903,52.7770065 L62.0766516,56.6452323 C62.6968774,57.2654581 63.7025226,57.2654581 64.3227484,56.6452323 C64.9429742,56.0250065 64.9429742,55.0193613 64.3227484,54.3991355 M48.3484258,45.9124258 C48.3484258,42.7925871 50.9696516,40.1713613 54.0894903,40.1713613 C57.209329,40.1713613 59.7052,42.6681677 59.7052,45.9124258 C59.7052,49.0332 57.209329,51.529071 54.0894903,51.529071 C50.8452323,51.529071 48.3484258,49.0332 48.3484258,45.9124258" id="Fill-3"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5.5 KiB |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "__MSG_appName__",
|
"name": "__MSG_appName__",
|
||||||
"short_name": "__MSG_appName__",
|
"short_name": "__MSG_appName__",
|
||||||
"version": "4.6.1",
|
"version": "4.7.0",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"author": "https://metamask.io",
|
"author": "https://metamask.io",
|
||||||
"description": "__MSG_appDescription__",
|
"description": "__MSG_appDescription__",
|
||||||
@ -68,6 +68,8 @@
|
|||||||
"matches": [
|
"matches": [
|
||||||
"https://metamask.io/*"
|
"https://metamask.io/*"
|
||||||
],
|
],
|
||||||
"ids": ["*"]
|
"ids": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -323,7 +323,7 @@ function setupController (initState, initLangCode) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A runtime.Port object, as provided by the browser:
|
* A runtime.Port object, as provided by the browser:
|
||||||
* @link https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/Port
|
* @see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/Port
|
||||||
* @typedef Port
|
* @typedef Port
|
||||||
* @type Object
|
* @type Object
|
||||||
*/
|
*/
|
||||||
|
@ -166,7 +166,7 @@ function documentElementCheck () {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the current domain is blacklisted
|
* Checks if the current domain is blacklisted
|
||||||
*
|
*
|
||||||
* @returns {boolean} {@code true} if the current domain is blacklisted
|
* @returns {boolean} {@code true} if the current domain is blacklisted
|
||||||
*/
|
*/
|
||||||
function blacklistedDomainCheck () {
|
function blacklistedDomainCheck () {
|
||||||
@ -174,6 +174,8 @@ function blacklistedDomainCheck () {
|
|||||||
'uscourts.gov',
|
'uscourts.gov',
|
||||||
'dropbox.com',
|
'dropbox.com',
|
||||||
'webbyawards.com',
|
'webbyawards.com',
|
||||||
|
'cdn.shopify.com/s/javascripts/tricorder/xtld-read-only-frame.html',
|
||||||
|
'adyen.com',
|
||||||
]
|
]
|
||||||
var currentUrl = window.location.href
|
var currentUrl = window.location.href
|
||||||
var currentRegex
|
var currentRegex
|
||||||
|
@ -13,19 +13,17 @@ class AddressBookController {
|
|||||||
* @param {object} opts Overrides the defaults for the initial state of this.store
|
* @param {object} opts Overrides the defaults for the initial state of this.store
|
||||||
* @property {array} opts.initState initializes the the state of the AddressBookController. Can contain an
|
* @property {array} opts.initState initializes the the state of the AddressBookController. Can contain an
|
||||||
* addressBook property to initialize the addressBook array
|
* addressBook property to initialize the addressBook array
|
||||||
* @param {KeyringController} keyringController (Soon to be deprecated) The keyringController used in the current
|
* @property {object} opts.preferencesStore the {@code PreferencesController} store
|
||||||
* MetamaskController. Contains the identities used in this AddressBookController.
|
|
||||||
* @property {object} store The the store of the current users address book
|
* @property {object} store The the store of the current users address book
|
||||||
* @property {array} store.addressBook An array of addresses and nicknames. These are set by the user when sending
|
* @property {array} store.addressBook An array of addresses and nicknames. These are set by the user when sending
|
||||||
* to a new address.
|
* to a new address.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
constructor (opts = {}, keyringController) {
|
constructor ({initState, preferencesStore}) {
|
||||||
const initState = extend({
|
this.store = new ObservableStore(extend({
|
||||||
addressBook: [],
|
addressBook: [],
|
||||||
}, opts.initState)
|
}, initState))
|
||||||
this.store = new ObservableStore(initState)
|
this._preferencesStore = preferencesStore
|
||||||
this.keyringController = keyringController
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -62,7 +60,7 @@ class AddressBookController {
|
|||||||
*/
|
*/
|
||||||
_addToAddressBook (address, name) {
|
_addToAddressBook (address, name) {
|
||||||
const addressBook = this._getAddressBook()
|
const addressBook = this._getAddressBook()
|
||||||
const identities = this._getIdentities()
|
const {identities} = this._preferencesStore.getState()
|
||||||
|
|
||||||
const addressBookIndex = addressBook.findIndex((element) => { return element.address.toLowerCase() === address.toLowerCase() || element.name === name })
|
const addressBookIndex = addressBook.findIndex((element) => { return element.address.toLowerCase() === address.toLowerCase() || element.name === name })
|
||||||
const identitiesIndex = Object.keys(identities).findIndex((element) => { return element.toLowerCase() === address.toLowerCase() })
|
const identitiesIndex = Object.keys(identities).findIndex((element) => { return element.toLowerCase() === address.toLowerCase() })
|
||||||
@ -95,19 +93,6 @@ class AddressBookController {
|
|||||||
_getAddressBook () {
|
_getAddressBook () {
|
||||||
return this.store.getState().addressBook
|
return this.store.getState().addressBook
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves identities from the keyring controller in order to avoid
|
|
||||||
* duplication
|
|
||||||
*
|
|
||||||
* @deprecated
|
|
||||||
* @returns {array} Returns the identies array from the keyringContoller's state
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
_getIdentities () {
|
|
||||||
return this.keyringController.memStore.getState().identities
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = AddressBookController
|
module.exports = AddressBookController
|
||||||
|
@ -13,20 +13,6 @@ const RINKEBY_DISPLAY_NAME = 'Rinkeby'
|
|||||||
const KOVAN_DISPLAY_NAME = 'Kovan'
|
const KOVAN_DISPLAY_NAME = 'Kovan'
|
||||||
const MAINNET_DISPLAY_NAME = 'Main Ethereum Network'
|
const MAINNET_DISPLAY_NAME = 'Main Ethereum Network'
|
||||||
|
|
||||||
const MAINNET_RPC_URL = 'https://mainnet.infura.io/metamask'
|
|
||||||
const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask'
|
|
||||||
const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
|
|
||||||
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
|
|
||||||
const LOCALHOST_RPC_URL = 'http://localhost:8545'
|
|
||||||
|
|
||||||
const MAINNET_RPC_URL_BETA = 'https://mainnet.infura.io/metamask2'
|
|
||||||
const ROPSTEN_RPC_URL_BETA = 'https://ropsten.infura.io/metamask2'
|
|
||||||
const KOVAN_RPC_URL_BETA = 'https://kovan.infura.io/metamask2'
|
|
||||||
const RINKEBY_RPC_URL_BETA = 'https://rinkeby.infura.io/metamask2'
|
|
||||||
|
|
||||||
const DEFAULT_NETWORK = 'rinkeby'
|
|
||||||
const OLD_UI_NETWORK_TYPE = 'network'
|
|
||||||
const BETA_UI_NETWORK_TYPE = 'networkBeta'
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ROPSTEN,
|
ROPSTEN,
|
||||||
@ -41,16 +27,4 @@ module.exports = {
|
|||||||
RINKEBY_DISPLAY_NAME,
|
RINKEBY_DISPLAY_NAME,
|
||||||
KOVAN_DISPLAY_NAME,
|
KOVAN_DISPLAY_NAME,
|
||||||
MAINNET_DISPLAY_NAME,
|
MAINNET_DISPLAY_NAME,
|
||||||
MAINNET_RPC_URL,
|
|
||||||
ROPSTEN_RPC_URL,
|
|
||||||
KOVAN_RPC_URL,
|
|
||||||
RINKEBY_RPC_URL,
|
|
||||||
LOCALHOST_RPC_URL,
|
|
||||||
MAINNET_RPC_URL_BETA,
|
|
||||||
ROPSTEN_RPC_URL_BETA,
|
|
||||||
KOVAN_RPC_URL_BETA,
|
|
||||||
RINKEBY_RPC_URL_BETA,
|
|
||||||
DEFAULT_NETWORK,
|
|
||||||
OLD_UI_NETWORK_TYPE,
|
|
||||||
BETA_UI_NETWORK_TYPE,
|
|
||||||
}
|
}
|
||||||
|
@ -14,52 +14,40 @@ const {
|
|||||||
RINKEBY,
|
RINKEBY,
|
||||||
KOVAN,
|
KOVAN,
|
||||||
MAINNET,
|
MAINNET,
|
||||||
OLD_UI_NETWORK_TYPE,
|
LOCALHOST,
|
||||||
DEFAULT_NETWORK,
|
|
||||||
} = require('./enums')
|
} = require('./enums')
|
||||||
const { getNetworkEndpoints } = require('./util')
|
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 METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
||||||
|
const testMode = (METAMASK_DEBUG || env === 'test')
|
||||||
|
|
||||||
|
const defaultProviderConfig = {
|
||||||
|
type: testMode ? RINKEBY : MAINNET,
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = class NetworkController extends EventEmitter {
|
module.exports = class NetworkController extends EventEmitter {
|
||||||
|
|
||||||
constructor (config) {
|
constructor (opts = {}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this._networkEndpointVersion = OLD_UI_NETWORK_TYPE
|
// parse options
|
||||||
this._networkEndpoints = getNetworkEndpoints(OLD_UI_NETWORK_TYPE)
|
const providerConfig = opts.provider || defaultProviderConfig
|
||||||
this._defaultRpc = this._networkEndpoints[DEFAULT_NETWORK]
|
// create stores
|
||||||
|
this.providerStore = new ObservableStore(providerConfig)
|
||||||
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider)
|
|
||||||
this.networkStore = new ObservableStore('loading')
|
this.networkStore = new ObservableStore('loading')
|
||||||
this.providerStore = new ObservableStore(config.provider)
|
|
||||||
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._proxy = createEventEmitterProxy()
|
||||||
|
|
||||||
this.on('networkDidChange', this.lookupNetwork)
|
this.on('networkDidChange', this.lookupNetwork)
|
||||||
}
|
}
|
||||||
|
|
||||||
async setNetworkEndpoints (version) {
|
|
||||||
if (version === this._networkEndpointVersion) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this._networkEndpointVersion = version
|
|
||||||
this._networkEndpoints = getNetworkEndpoints(version)
|
|
||||||
this._defaultRpc = this._networkEndpoints[DEFAULT_NETWORK]
|
|
||||||
const { type } = this.getProviderConfig()
|
|
||||||
|
|
||||||
return this.setProviderType(type, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeProvider (_providerParams) {
|
initializeProvider (_providerParams) {
|
||||||
this._baseProviderParams = _providerParams
|
this._baseProviderParams = _providerParams
|
||||||
const { type, rpcTarget } = this.providerStore.getState()
|
const { type, rpcTarget } = this.providerStore.getState()
|
||||||
// map rpcTarget to rpcUrl
|
this._configureProvider({ type, rpcTarget })
|
||||||
const opts = {
|
|
||||||
type,
|
|
||||||
rpcUrl: rpcTarget,
|
|
||||||
}
|
|
||||||
this._configureProvider(opts)
|
|
||||||
this._proxy.on('block', this._logBlock.bind(this))
|
this._proxy.on('block', this._logBlock.bind(this))
|
||||||
this._proxy.on('error', this.verifyNetwork.bind(this))
|
this._proxy.on('error', this.verifyNetwork.bind(this))
|
||||||
this.ethQuery = new EthQuery(this._proxy)
|
this.ethQuery = new EthQuery(this._proxy)
|
||||||
@ -96,45 +84,27 @@ module.exports = class NetworkController extends EventEmitter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setRpcTarget (rpcUrl) {
|
setRpcTarget (rpcTarget) {
|
||||||
this.providerStore.updateState({
|
const providerConfig = {
|
||||||
type: 'rpc',
|
type: 'rpc',
|
||||||
rpcTarget: rpcUrl,
|
rpcTarget,
|
||||||
})
|
|
||||||
this._switchNetwork({ rpcUrl })
|
|
||||||
}
|
|
||||||
|
|
||||||
getCurrentRpcAddress () {
|
|
||||||
const provider = this.getProviderConfig()
|
|
||||||
if (!provider) return null
|
|
||||||
return this.getRpcAddressForType(provider.type)
|
|
||||||
}
|
|
||||||
|
|
||||||
async setProviderType (type, forceUpdate = false) {
|
|
||||||
assert(type !== 'rpc', `NetworkController.setProviderType - cannot connect by type "rpc"`)
|
|
||||||
// skip if type already matches
|
|
||||||
if (type === this.getProviderConfig().type && !forceUpdate) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
this.providerStore.updateState(providerConfig)
|
||||||
|
this._switchNetwork(providerConfig)
|
||||||
|
}
|
||||||
|
|
||||||
const rpcTarget = this.getRpcAddressForType(type)
|
async setProviderType (type) {
|
||||||
assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`)
|
assert.notEqual(type, 'rpc', `NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`)
|
||||||
this.providerStore.updateState({ type, rpcTarget })
|
assert(INFURA_PROVIDER_TYPES.includes(type) || type === LOCALHOST, `NetworkController - Unknown rpc type "${type}"`)
|
||||||
this._switchNetwork({ type })
|
const providerConfig = { type }
|
||||||
|
this.providerStore.updateState(providerConfig)
|
||||||
|
this._switchNetwork(providerConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
getProviderConfig () {
|
getProviderConfig () {
|
||||||
return this.providerStore.getState()
|
return this.providerStore.getState()
|
||||||
}
|
}
|
||||||
|
|
||||||
getRpcAddressForType (type, provider = this.getProviderConfig()) {
|
|
||||||
if (this._networkEndpoints[type]) {
|
|
||||||
return this._networkEndpoints[type]
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider && provider.rpcTarget ? provider.rpcTarget : this._defaultRpc
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Private
|
// Private
|
||||||
//
|
//
|
||||||
@ -146,32 +116,27 @@ module.exports = class NetworkController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_configureProvider (opts) {
|
_configureProvider (opts) {
|
||||||
// type-based rpc endpoints
|
const { type, rpcTarget } = opts
|
||||||
const { type } = opts
|
// infura type-based endpoints
|
||||||
if (type) {
|
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
|
||||||
// type-based infura rpc endpoints
|
if (isInfura) {
|
||||||
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
|
this._configureInfuraProvider(opts)
|
||||||
opts.rpcUrl = this.getRpcAddressForType(type)
|
// other type-based rpc endpoints
|
||||||
if (isInfura) {
|
} else if (type === LOCALHOST) {
|
||||||
this._configureInfuraProvider(opts)
|
this._configureStandardProvider({ rpcUrl: LOCALHOST_RPC_URL })
|
||||||
// other type-based rpc endpoints
|
|
||||||
} else {
|
|
||||||
this._configureStandardProvider(opts)
|
|
||||||
}
|
|
||||||
// url-based rpc endpoints
|
// url-based rpc endpoints
|
||||||
|
} else if (type === 'rpc'){
|
||||||
|
this._configureStandardProvider({ rpcUrl: rpcTarget })
|
||||||
} else {
|
} else {
|
||||||
this._configureStandardProvider(opts)
|
throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_configureInfuraProvider (opts) {
|
_configureInfuraProvider ({ type }) {
|
||||||
log.info('_configureInfuraProvider', opts)
|
log.info('_configureInfuraProvider', type)
|
||||||
const infuraProvider = createInfuraProvider({
|
const infuraProvider = createInfuraProvider({ network: type })
|
||||||
network: opts.type,
|
|
||||||
})
|
|
||||||
const infuraSubprovider = new SubproviderFromProvider(infuraProvider)
|
const infuraSubprovider = new SubproviderFromProvider(infuraProvider)
|
||||||
const providerParams = extend(this._baseProviderParams, {
|
const providerParams = extend(this._baseProviderParams, {
|
||||||
rpcUrl: opts.rpcUrl,
|
|
||||||
engineParams: {
|
engineParams: {
|
||||||
pollingInterval: 8000,
|
pollingInterval: 8000,
|
||||||
blockTrackerProvider: infuraProvider,
|
blockTrackerProvider: infuraProvider,
|
||||||
|
@ -3,7 +3,6 @@ const {
|
|||||||
RINKEBY,
|
RINKEBY,
|
||||||
KOVAN,
|
KOVAN,
|
||||||
MAINNET,
|
MAINNET,
|
||||||
LOCALHOST,
|
|
||||||
ROPSTEN_CODE,
|
ROPSTEN_CODE,
|
||||||
RINKEYBY_CODE,
|
RINKEYBY_CODE,
|
||||||
KOVAN_CODE,
|
KOVAN_CODE,
|
||||||
@ -11,17 +10,6 @@ const {
|
|||||||
RINKEBY_DISPLAY_NAME,
|
RINKEBY_DISPLAY_NAME,
|
||||||
KOVAN_DISPLAY_NAME,
|
KOVAN_DISPLAY_NAME,
|
||||||
MAINNET_DISPLAY_NAME,
|
MAINNET_DISPLAY_NAME,
|
||||||
MAINNET_RPC_URL,
|
|
||||||
ROPSTEN_RPC_URL,
|
|
||||||
KOVAN_RPC_URL,
|
|
||||||
RINKEBY_RPC_URL,
|
|
||||||
LOCALHOST_RPC_URL,
|
|
||||||
MAINNET_RPC_URL_BETA,
|
|
||||||
ROPSTEN_RPC_URL_BETA,
|
|
||||||
KOVAN_RPC_URL_BETA,
|
|
||||||
RINKEBY_RPC_URL_BETA,
|
|
||||||
OLD_UI_NETWORK_TYPE,
|
|
||||||
BETA_UI_NETWORK_TYPE,
|
|
||||||
} = require('./enums')
|
} = require('./enums')
|
||||||
|
|
||||||
const networkToNameMap = {
|
const networkToNameMap = {
|
||||||
@ -34,32 +22,8 @@ const networkToNameMap = {
|
|||||||
[KOVAN_CODE]: KOVAN_DISPLAY_NAME,
|
[KOVAN_CODE]: KOVAN_DISPLAY_NAME,
|
||||||
}
|
}
|
||||||
|
|
||||||
const networkEndpointsMap = {
|
|
||||||
[OLD_UI_NETWORK_TYPE]: {
|
|
||||||
[LOCALHOST]: LOCALHOST_RPC_URL,
|
|
||||||
[MAINNET]: MAINNET_RPC_URL,
|
|
||||||
[ROPSTEN]: ROPSTEN_RPC_URL,
|
|
||||||
[KOVAN]: KOVAN_RPC_URL,
|
|
||||||
[RINKEBY]: RINKEBY_RPC_URL,
|
|
||||||
},
|
|
||||||
[BETA_UI_NETWORK_TYPE]: {
|
|
||||||
[LOCALHOST]: LOCALHOST_RPC_URL,
|
|
||||||
[MAINNET]: MAINNET_RPC_URL_BETA,
|
|
||||||
[ROPSTEN]: ROPSTEN_RPC_URL_BETA,
|
|
||||||
[KOVAN]: KOVAN_RPC_URL_BETA,
|
|
||||||
[RINKEBY]: RINKEBY_RPC_URL_BETA,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const getNetworkDisplayName = key => networkToNameMap[key]
|
const getNetworkDisplayName = key => networkToNameMap[key]
|
||||||
|
|
||||||
const getNetworkEndpoints = (networkType = OLD_UI_NETWORK_TYPE) => {
|
|
||||||
return {
|
|
||||||
...networkEndpointsMap[networkType],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getNetworkDisplayName,
|
getNetworkDisplayName,
|
||||||
getNetworkEndpoints,
|
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ class PreferencesController {
|
|||||||
useBlockie: false,
|
useBlockie: false,
|
||||||
featureFlags: {},
|
featureFlags: {},
|
||||||
currentLocale: opts.initLangCode,
|
currentLocale: opts.initLangCode,
|
||||||
|
identities: {},
|
||||||
}, opts.initState)
|
}, opts.initState)
|
||||||
this.store = new ObservableStore(initState)
|
this.store = new ObservableStore(initState)
|
||||||
}
|
}
|
||||||
@ -62,6 +63,16 @@ class PreferencesController {
|
|||||||
this.store.updateState({ currentLocale: key })
|
this.store.updateState({ currentLocale: key })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAddresses (addresses) {
|
||||||
|
const oldIdentities = this.store.getState().identities
|
||||||
|
const identities = addresses.reduce((ids, address, index) => {
|
||||||
|
const oldId = oldIdentities[address] || {}
|
||||||
|
ids[address] = {name: `Account ${index + 1}`, address, ...oldId}
|
||||||
|
return ids
|
||||||
|
}, {})
|
||||||
|
this.store.updateState({ identities })
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setter for the `selectedAddress` property
|
* Setter for the `selectedAddress` property
|
||||||
*
|
*
|
||||||
@ -155,6 +166,21 @@ class PreferencesController {
|
|||||||
return this.store.getState().tokens
|
return this.store.getState().tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a custom label for an account
|
||||||
|
* @param {string} account the account to set a label for
|
||||||
|
* @param {string} label the custom label for the account
|
||||||
|
* @return {Promise<string>}
|
||||||
|
*/
|
||||||
|
setAccountLabel (account, label) {
|
||||||
|
const address = normalizeAddress(account)
|
||||||
|
const {identities} = this.store.getState()
|
||||||
|
identities[address] = identities[address] || {}
|
||||||
|
identities[address].name = label
|
||||||
|
this.store.updateState({ identities })
|
||||||
|
return Promise.resolve(label)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets an updated rpc list from this.addToFrequentRpcList() and sets the `frequentRpcList` to this update list.
|
* Gets an updated rpc list from this.addToFrequentRpcList() and sets the `frequentRpcList` to this update list.
|
||||||
*
|
*
|
||||||
@ -189,8 +215,8 @@ class PreferencesController {
|
|||||||
* 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 2. 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.
|
||||||
* @returns {Promise<array>} The updated frequentRpcList.
|
* @returns {Promise<array>} The updated frequentRpcList.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
addToFrequentRpcList (_url) {
|
addToFrequentRpcList (_url) {
|
||||||
|
@ -119,29 +119,21 @@ class RecentBlocksController {
|
|||||||
*/
|
*/
|
||||||
async backfill() {
|
async backfill() {
|
||||||
this.blockTracker.once('block', async (block) => {
|
this.blockTracker.once('block', async (block) => {
|
||||||
let blockNum = block.number
|
const currentBlockNumber = Number.parseInt(block.number, 16)
|
||||||
let recentBlocks
|
const blocksToFetch = Math.min(currentBlockNumber, this.historyLength)
|
||||||
let state = this.store.getState()
|
const prevBlockNumber = currentBlockNumber - 1
|
||||||
recentBlocks = state.recentBlocks
|
const targetBlockNumbers = Array(blocksToFetch).fill().map((_, index) => prevBlockNumber - index)
|
||||||
|
await Promise.all(targetBlockNumbers.map(async (targetBlockNumber) => {
|
||||||
while (recentBlocks.length < this.historyLength) {
|
|
||||||
try {
|
try {
|
||||||
let blockNumBn = new BN(blockNum.substr(2), 16)
|
const newBlock = await this.getBlockByNumber(targetBlockNumber)
|
||||||
const newNum = blockNumBn.subn(1).toString(10)
|
|
||||||
const newBlock = await this.getBlockByNumber(newNum)
|
|
||||||
|
|
||||||
if (newBlock) {
|
if (newBlock) {
|
||||||
this.backfillBlock(newBlock)
|
this.backfillBlock(newBlock)
|
||||||
blockNum = newBlock.number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state = this.store.getState()
|
|
||||||
recentBlocks = state.recentBlocks
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error(e)
|
log.error(e)
|
||||||
}
|
}
|
||||||
await this.wait()
|
}))
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,26 +25,31 @@ function migrateFromSnapshotsToDiffs (longHistory) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
generates an array of history objects sense the previous state.
|
Generates an array of history objects sense the previous state.
|
||||||
The object has the keys opp(the operation preformed),
|
The object has the keys
|
||||||
path(the key and if a nested object then each key will be seperated with a `/`)
|
op (the operation performed),
|
||||||
value
|
path (the key and if a nested object then each key will be seperated with a `/`)
|
||||||
with the first entry having the note
|
value
|
||||||
|
with the first entry having the note and a timestamp when the change took place
|
||||||
@param previousState {object} - the previous state of the object
|
@param previousState {object} - the previous state of the object
|
||||||
@param newState {object} - the update object
|
@param newState {object} - the update object
|
||||||
@param note {string} - a optional note for the state change
|
@param note {string} - a optional note for the state change
|
||||||
@reurns {array}
|
@returns {array}
|
||||||
*/
|
*/
|
||||||
function generateHistoryEntry (previousState, newState, note) {
|
function generateHistoryEntry (previousState, newState, note) {
|
||||||
const entry = jsonDiffer.compare(previousState, newState)
|
const entry = jsonDiffer.compare(previousState, newState)
|
||||||
// Add a note to the first op, since it breaks if we append it to the entry
|
// Add a note to the first op, since it breaks if we append it to the entry
|
||||||
if (note && entry[0]) entry[0].note = note
|
if (entry[0]) {
|
||||||
|
if (note) entry[0].note = note
|
||||||
|
|
||||||
|
entry[0].timestamp = Date.now()
|
||||||
|
}
|
||||||
return entry
|
return entry
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Recovers previous txMeta state obj
|
Recovers previous txMeta state obj
|
||||||
@return {object}
|
@returns {object}
|
||||||
*/
|
*/
|
||||||
function replayHistory (_shortHistory) {
|
function replayHistory (_shortHistory) {
|
||||||
const shortHistory = clone(_shortHistory)
|
const shortHistory = clone(_shortHistory)
|
||||||
|
@ -159,7 +159,7 @@ class TransactionStateManager extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
updates the txMeta in the list and adds a history entry
|
updates the txMeta in the list and adds a history entry
|
||||||
@param txMeta {Object} - the txMeta to update
|
@param txMeta {Object} - the txMeta to update
|
||||||
@param [note] {string} - a not about the update for history
|
@param [note] {string} - a note about the update for history
|
||||||
*/
|
*/
|
||||||
updateTx (txMeta, note) {
|
updateTx (txMeta, note) {
|
||||||
// validate txParams
|
// validate txParams
|
||||||
@ -263,7 +263,7 @@ class TransactionStateManager extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
getTxsByMetaData (key, value, txList = this.getTxList()) {
|
getTxsByMetaData (key, value, txList = this.getTxList()) {
|
||||||
return txList.filter((txMeta) => {
|
return txList.filter((txMeta) => {
|
||||||
if (txMeta.txParams[key]) {
|
if (key in txMeta.txParams) {
|
||||||
return txMeta.txParams[key] === value
|
return txMeta.txParams[key] === value
|
||||||
} else {
|
} else {
|
||||||
return txMeta[key] === value
|
return txMeta[key] === value
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
// test and development environment variables
|
|
||||||
const env = process.env.METAMASK_ENV
|
|
||||||
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
|
||||||
const { DEFAULT_NETWORK, MAINNET } = require('./controllers/network/enums')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} FirstTimeState
|
* @typedef {Object} FirstTimeState
|
||||||
@ -14,11 +10,6 @@ const { DEFAULT_NETWORK, MAINNET } = require('./controllers/network/enums')
|
|||||||
*/
|
*/
|
||||||
const initialState = {
|
const initialState = {
|
||||||
config: {},
|
config: {},
|
||||||
NetworkController: {
|
|
||||||
provider: {
|
|
||||||
type: (METAMASK_DEBUG || env === 'test') ? DEFAULT_NETWORK : MAINNET,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = initialState
|
module.exports = initialState
|
||||||
|
66
app/scripts/lib/createErrorMiddleware.js
Normal file
66
app/scripts/lib/createErrorMiddleware.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = createErrorMiddleware
|
@ -2,6 +2,12 @@ const extension = require('extensionizer')
|
|||||||
const promisify = require('pify')
|
const promisify = require('pify')
|
||||||
const allLocales = require('../../_locales/index.json')
|
const allLocales = require('../../_locales/index.json')
|
||||||
|
|
||||||
|
const isSupported = extension.i18n && extension.i18n.getAcceptLanguages
|
||||||
|
const getPreferredLocales = isSupported ? promisify(
|
||||||
|
extension.i18n.getAcceptLanguages,
|
||||||
|
{ errorFirst: false }
|
||||||
|
) : async () => []
|
||||||
|
|
||||||
const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().replace('_', '-'))
|
const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().replace('_', '-'))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -12,10 +18,7 @@ const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().r
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async function getFirstPreferredLangCode () {
|
async function getFirstPreferredLangCode () {
|
||||||
const userPreferredLocaleCodes = await promisify(
|
const userPreferredLocaleCodes = await getPreferredLocales()
|
||||||
extension.i18n.getAcceptLanguages,
|
|
||||||
{ errorFirst: false }
|
|
||||||
)()
|
|
||||||
const firstPreferredLangCode = userPreferredLocaleCodes
|
const firstPreferredLangCode = userPreferredLocaleCodes
|
||||||
.map(code => code.toLowerCase())
|
.map(code => code.toLowerCase())
|
||||||
.find(code => existingLocaleCodes.includes(code))
|
.find(code => existingLocaleCodes.includes(code))
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const pump = require('pump')
|
const pump = require('pump')
|
||||||
const RpcEngine = require('json-rpc-engine')
|
const RpcEngine = require('json-rpc-engine')
|
||||||
|
const createErrorMiddleware = require('./createErrorMiddleware')
|
||||||
const createIdRemapMiddleware = require('json-rpc-engine/src/idRemapMiddleware')
|
const createIdRemapMiddleware = require('json-rpc-engine/src/idRemapMiddleware')
|
||||||
const createStreamMiddleware = require('json-rpc-middleware-stream')
|
const createStreamMiddleware = require('json-rpc-middleware-stream')
|
||||||
const LocalStorageStore = require('obs-store')
|
const LocalStorageStore = require('obs-store')
|
||||||
@ -44,6 +45,7 @@ function MetamaskInpageProvider (connectionStream) {
|
|||||||
// handle sendAsync requests via dapp-side rpc engine
|
// handle sendAsync requests via dapp-side rpc engine
|
||||||
const rpcEngine = new RpcEngine()
|
const rpcEngine = new RpcEngine()
|
||||||
rpcEngine.push(createIdRemapMiddleware())
|
rpcEngine.push(createIdRemapMiddleware())
|
||||||
|
rpcEngine.push(createErrorMiddleware())
|
||||||
rpcEngine.push(streamMiddleware)
|
rpcEngine.push(streamMiddleware)
|
||||||
self.rpcEngine = rpcEngine
|
self.rpcEngine = rpcEngine
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ function setupRaven(opts) {
|
|||||||
const report = opts.data
|
const report = opts.data
|
||||||
try {
|
try {
|
||||||
// handle error-like non-error exceptions
|
// handle error-like non-error exceptions
|
||||||
nonErrorException(report)
|
rewriteErrorLikeExceptions(report)
|
||||||
// simplify certain complex error messages (e.g. Ethjs)
|
// simplify certain complex error messages (e.g. Ethjs)
|
||||||
simplifyErrorMessages(report)
|
simplifyErrorMessages(report)
|
||||||
// modify report urls
|
// modify report urls
|
||||||
@ -42,27 +42,35 @@ function setupRaven(opts) {
|
|||||||
return Raven
|
return Raven
|
||||||
}
|
}
|
||||||
|
|
||||||
function nonErrorException(report) {
|
function rewriteErrorLikeExceptions(report) {
|
||||||
// handle errors that lost their error-ness in serialization
|
// handle errors that lost their error-ness in serialization (e.g. dnode)
|
||||||
if (report.message.includes('Non-Error exception captured with keys: message')) {
|
rewriteErrorMessages(report, (errorMessage) => {
|
||||||
if (!(report.extra && report.extra.__serialized__)) return
|
if (!errorMessage.includes('Non-Error exception captured with keys:')) return errorMessage
|
||||||
report.message = `Non-Error Exception: ${report.extra.__serialized__.message}`
|
if (!(report.extra && report.extra.__serialized__ && report.extra.__serialized__.message)) return errorMessage
|
||||||
}
|
return `Non-Error Exception: ${report.extra.__serialized__.message}`
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function simplifyErrorMessages(report) {
|
function simplifyErrorMessages(report) {
|
||||||
|
rewriteErrorMessages(report, (errorMessage) => {
|
||||||
|
// simplify ethjs error messages
|
||||||
|
errorMessage = extractEthjsErrorMessage(errorMessage)
|
||||||
|
// simplify 'Transaction Failed: known transaction'
|
||||||
|
if (errorMessage.indexOf('Transaction Failed: known transaction') === 0) {
|
||||||
|
// cut the hash from the error message
|
||||||
|
errorMessage = 'Transaction Failed: known transaction'
|
||||||
|
}
|
||||||
|
return errorMessage
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function rewriteErrorMessages(report, rewriteFn) {
|
||||||
|
// rewrite top level message
|
||||||
|
report.message = rewriteFn(report.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 => {
|
||||||
let errorMessage = item.value
|
item.value = rewriteFn(item.value)
|
||||||
// simplify ethjs error messages
|
|
||||||
errorMessage = extractEthjsErrorMessage(errorMessage)
|
|
||||||
// simplify 'Transaction Failed: known transaction'
|
|
||||||
if (errorMessage.indexOf('Transaction Failed: known transaction') === 0) {
|
|
||||||
// cut the hash from the error message
|
|
||||||
errorMessage = 'Transaction Failed: known transaction'
|
|
||||||
}
|
|
||||||
// finalize
|
|
||||||
item.value = errorMessage
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
// address book controller
|
// address book controller
|
||||||
this.addressBookController = new AddressBookController({
|
this.addressBookController = new AddressBookController({
|
||||||
initState: initState.AddressBookController,
|
initState: initState.AddressBookController,
|
||||||
}, this.keyringController)
|
preferencesStore: this.preferencesController.store,
|
||||||
|
})
|
||||||
|
|
||||||
// tx mgmt
|
// tx mgmt
|
||||||
this.txController = new TransactionController({
|
this.txController = new TransactionController({
|
||||||
@ -350,13 +351,12 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
verifySeedPhrase: nodeify(this.verifySeedPhrase, this),
|
verifySeedPhrase: nodeify(this.verifySeedPhrase, this),
|
||||||
clearSeedWordCache: this.clearSeedWordCache.bind(this),
|
clearSeedWordCache: this.clearSeedWordCache.bind(this),
|
||||||
resetAccount: nodeify(this.resetAccount, this),
|
resetAccount: nodeify(this.resetAccount, this),
|
||||||
importAccountWithStrategy: this.importAccountWithStrategy.bind(this),
|
importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this),
|
||||||
|
|
||||||
// vault management
|
// vault management
|
||||||
submitPassword: nodeify(keyringController.submitPassword, keyringController),
|
submitPassword: nodeify(keyringController.submitPassword, keyringController),
|
||||||
|
|
||||||
// network management
|
// network management
|
||||||
setNetworkEndpoints: nodeify(networkController.setNetworkEndpoints, networkController),
|
|
||||||
setProviderType: nodeify(networkController.setProviderType, networkController),
|
setProviderType: nodeify(networkController.setProviderType, networkController),
|
||||||
setCustomRpc: nodeify(this.setCustomRpc, this),
|
setCustomRpc: nodeify(this.setCustomRpc, this),
|
||||||
|
|
||||||
@ -365,6 +365,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
addToken: nodeify(preferencesController.addToken, preferencesController),
|
addToken: nodeify(preferencesController.addToken, preferencesController),
|
||||||
removeToken: nodeify(preferencesController.removeToken, preferencesController),
|
removeToken: nodeify(preferencesController.removeToken, preferencesController),
|
||||||
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
|
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
|
||||||
|
setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController),
|
||||||
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
|
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
|
||||||
|
|
||||||
// AddressController
|
// AddressController
|
||||||
@ -375,7 +376,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
createNewVaultAndKeychain: nodeify(this.createNewVaultAndKeychain, this),
|
createNewVaultAndKeychain: nodeify(this.createNewVaultAndKeychain, this),
|
||||||
createNewVaultAndRestore: nodeify(this.createNewVaultAndRestore, this),
|
createNewVaultAndRestore: nodeify(this.createNewVaultAndRestore, this),
|
||||||
addNewKeyring: nodeify(keyringController.addNewKeyring, keyringController),
|
addNewKeyring: nodeify(keyringController.addNewKeyring, keyringController),
|
||||||
saveAccountLabel: nodeify(keyringController.saveAccountLabel, keyringController),
|
|
||||||
exportAccount: nodeify(keyringController.exportAccount, keyringController),
|
exportAccount: nodeify(keyringController.exportAccount, keyringController),
|
||||||
|
|
||||||
// txController
|
// txController
|
||||||
@ -383,6 +383,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),
|
||||||
|
getFilteredTxList: nodeify(txController.getFilteredTxList, txController),
|
||||||
|
|
||||||
// messageManager
|
// messageManager
|
||||||
signMessage: nodeify(this.signMessage, this),
|
signMessage: nodeify(this.signMessage, this),
|
||||||
@ -434,7 +435,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
vault = await this.keyringController.createNewVaultAndKeychain(password)
|
vault = await this.keyringController.createNewVaultAndKeychain(password)
|
||||||
this.selectFirstIdentity(vault)
|
const accounts = await this.keyringController.getAccounts()
|
||||||
|
this.preferencesController.setAddresses(accounts)
|
||||||
|
this.selectFirstIdentity()
|
||||||
}
|
}
|
||||||
release()
|
release()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -454,7 +457,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
const release = await this.createVaultMutex.acquire()
|
const release = await this.createVaultMutex.acquire()
|
||||||
try {
|
try {
|
||||||
const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
|
const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
|
||||||
this.selectFirstIdentity(vault)
|
const accounts = await this.keyringController.getAccounts()
|
||||||
|
this.preferencesController.setAddresses(accounts)
|
||||||
|
this.selectFirstIdentity()
|
||||||
release()
|
release()
|
||||||
return vault
|
return vault
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -472,12 +477,10 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the first Identiy from the passed Vault and selects the related address
|
* Sets the first address in the state to the selected address
|
||||||
*
|
|
||||||
* @param {} vault
|
|
||||||
*/
|
*/
|
||||||
selectFirstIdentity (vault) {
|
selectFirstIdentity () {
|
||||||
const { identities } = vault
|
const { identities } = this.preferencesController.store.getState()
|
||||||
const address = Object.keys(identities)[0]
|
const address = Object.keys(identities)[0]
|
||||||
this.preferencesController.setSelectedAddress(address)
|
this.preferencesController.setSelectedAddress(address)
|
||||||
}
|
}
|
||||||
@ -503,13 +506,15 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
|
|
||||||
await this.verifySeedPhrase()
|
await this.verifySeedPhrase()
|
||||||
|
|
||||||
|
this.preferencesController.setAddresses(newAccounts)
|
||||||
newAccounts.forEach((address) => {
|
newAccounts.forEach((address) => {
|
||||||
if (!oldAccounts.includes(address)) {
|
if (!oldAccounts.includes(address)) {
|
||||||
this.preferencesController.setSelectedAddress(address)
|
this.preferencesController.setSelectedAddress(address)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return keyState
|
const {identities} = this.preferencesController.store.getState()
|
||||||
|
return {...keyState, identities}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -604,15 +609,15 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
* @param {any} args - The data required by that strategy to import an account.
|
* @param {any} args - The data required by that strategy to import an account.
|
||||||
* @param {Function} cb - A callback function called with a state update on success.
|
* @param {Function} cb - A callback function called with a state update on success.
|
||||||
*/
|
*/
|
||||||
importAccountWithStrategy (strategy, args, cb) {
|
async importAccountWithStrategy (strategy, args) {
|
||||||
accountImporter.importAccount(strategy, args)
|
const privateKey = await accountImporter.importAccount(strategy, args)
|
||||||
.then((privateKey) => {
|
const keyring = await this.keyringController.addNewKeyring('Simple Key Pair', [ privateKey ])
|
||||||
return this.keyringController.addNewKeyring('Simple Key Pair', [ privateKey ])
|
const accounts = await keyring.getAccounts()
|
||||||
})
|
// update accounts in preferences controller
|
||||||
.then(keyring => keyring.getAccounts())
|
const allAccounts = await this.keyringController.getAccounts()
|
||||||
.then((accounts) => this.preferencesController.setSelectedAddress(accounts[0]))
|
this.preferencesController.setAddresses(allAccounts)
|
||||||
.then(() => { cb(null, this.keyringController.fullUpdate()) })
|
// set new account as selected
|
||||||
.catch((reason) => { cb(reason) })
|
await this.preferencesController.setSelectedAddress(accounts[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
47
app/scripts/migrations/026.js
Normal file
47
app/scripts/migrations/026.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
const version = 26
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
This migration moves the identities stored in the KeyringController
|
||||||
|
into the PreferencesController
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const clone = require('clone')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
version,
|
||||||
|
migrate (originalVersionedData) {
|
||||||
|
const versionedData = clone(originalVersionedData)
|
||||||
|
versionedData.meta.version = version
|
||||||
|
try {
|
||||||
|
const state = versionedData.data
|
||||||
|
versionedData.data = transformState(state)
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`MetaMask Migration #${version}` + err.stack)
|
||||||
|
return Promise.reject(err)
|
||||||
|
}
|
||||||
|
return Promise.resolve(versionedData)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformState (state) {
|
||||||
|
if (!state.KeyringController || !state.PreferencesController) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.KeyringController.walletNicknames) {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
state.PreferencesController.identities = Object.keys(state.KeyringController.walletNicknames)
|
||||||
|
.reduce((identities, address) => {
|
||||||
|
identities[address] = {
|
||||||
|
name: state.KeyringController.walletNicknames[address],
|
||||||
|
address,
|
||||||
|
}
|
||||||
|
return identities
|
||||||
|
}, {})
|
||||||
|
delete state.KeyringController.walletNicknames
|
||||||
|
return state
|
||||||
|
}
|
@ -36,4 +36,5 @@ module.exports = [
|
|||||||
require('./023'),
|
require('./023'),
|
||||||
require('./024'),
|
require('./024'),
|
||||||
require('./025'),
|
require('./025'),
|
||||||
|
require('./026'),
|
||||||
]
|
]
|
||||||
|
@ -1,6 +1,16 @@
|
|||||||
# Guide to Porting MetaMask to a New Environment
|
# Guide to Porting MetaMask to a New Environment
|
||||||
|
|
||||||
MetaMask has been under continuous development for nearly two years now, and we’ve gradually discovered some very useful abstractions, that have allowed us to grow more easily. A couple of those layers together allow MetaMask to be ported to new environments and contexts increasingly easily.
|
MetaMask has been under continuous development for nearly two years now, and we’ve gradually discovered some useful abstractions that have allowed us to grow more easily. A couple of those layers together allow MetaMask to be ported to new environments and contexts increasingly easily (although it still could be easier, and please let us know if you get stuck!)
|
||||||
|
|
||||||
|
Before we get started, it's worth becoming familiar with our basic architecture:
|
||||||
|
|
||||||
|
![metamask-architecture-diagram](./architecture.png)
|
||||||
|
|
||||||
|
The `metamask-background` describes the file at `app/scripts/background.js`, which is the web extension singleton. This context instantiates an instance of the `MetaMask Controller`, which represents the user's accounts, a connection to the blockchain, and the interaction with new Dapps.
|
||||||
|
|
||||||
|
When a new site is visited, the WebExtension creates a new `ContentScript` in that page's context, which can be seen at `app/scripts/contentscript.js`. This script represents a per-page setup process, which creates the per-page `web3` api, connects it to the background script via the Port API (wrapped in a [stream abstraction](https://github.com/substack/stream-handbook)), and injected into the DOM before anything loads.
|
||||||
|
|
||||||
|
The most confusing part about porting MetaMask to a new platform is the way we provide the Web3 API over a series of streams between contexts. Once you understand how we create the [InpageProvider](../app/scripts/lib/inpage-provider.js) in the [inpage.js script](../app/scripts/inpage.js), you will be able to understand how the [port-stream](../app/scripts/lib/port-stream.js) is just a thin wrapper around the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage), and a similar stream API can be wrapped around any communication channel to communicate with the `MetaMaskController` via its `setupUntrustedCommunication(stream, domain)` method.
|
||||||
|
|
||||||
### The MetaMask Controller
|
### The MetaMask Controller
|
||||||
|
|
||||||
@ -90,3 +100,4 @@ If streams seem new and confusing to you, that's ok, they can seem strange at fi
|
|||||||
## Conclusion
|
## Conclusion
|
||||||
|
|
||||||
I hope this has been helpful to you! If you have any other questionsm, or points you think need clarification in this guide, please [open an issue on our GitHub](https://github.com/MetaMask/metamask-plugin/issues/new)!
|
I hope this has been helpful to you! If you have any other questionsm, or points you think need clarification in this guide, please [open an issue on our GitHub](https://github.com/MetaMask/metamask-plugin/issues/new)!
|
||||||
|
|
||||||
|
96
docs/send-screen-QA-checklist.md
Normal file
96
docs/send-screen-QA-checklist.md
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# Send screen QA checklist:
|
||||||
|
|
||||||
|
This checklist can be to guide QA of the send screen. It can also be used to guide e2e tests for the send screen.
|
||||||
|
|
||||||
|
Once all of these are QA verified on master, resolutions to any bugs related to the send screen should include and update to this list.
|
||||||
|
|
||||||
|
Additional features or functionality on the send screen should include an update to this list.
|
||||||
|
|
||||||
|
## Send Eth mode
|
||||||
|
- [ ] **Header** _It should:_
|
||||||
|
- [ ] have title "Send ETH"
|
||||||
|
- [ ] have sub title "Only send ETH to an Ethereum address."
|
||||||
|
- [ ] return user to main screen when top right X is clicked
|
||||||
|
- [ ] **From row** _It should:_
|
||||||
|
- [ ] show the currently selected account by default
|
||||||
|
- [ ] show a dropdown with all of the users accounts
|
||||||
|
- [ ] contain the following info for each account: identicon, account name, balance in ETH, balance in current currency
|
||||||
|
- [ ] change the account selected in the dropdown (but not the app-wide selected account) when one in the dropdown is clicked
|
||||||
|
- [ ] close the dropdown, without changing the dropdown selected account, when the dropdown is open and then a click happens outside it
|
||||||
|
- [ ] **To row** _It should:_
|
||||||
|
- [ ] Show a placeholder with the text 'Recipient Address' by default
|
||||||
|
- [ ] Show, when clicked, a dropdown list of all 'to accounts': the users accounts, plus any other accounts they have previously sent to
|
||||||
|
- [ ] Show account address, and account name if it exists, of each item in the dropdown list
|
||||||
|
- [ ] Show a dropdown list of all to accounts (see above) whose address matches an address currently being typed in
|
||||||
|
- [ ] Set the input text to the address of an account clicked in the dropdown list, and also hide the dropdown
|
||||||
|
- [ ] Hide the dropdown without changing what is in the input if the user clicks outside the dropdown list while it is open
|
||||||
|
- [ ] Select the text in the input (i.e. the address) if an address is displayed and then clicked
|
||||||
|
- [ ] Show a 'required' error if the dropdown is opened but no account is selected
|
||||||
|
- [ ] Show an 'invalid address' error if text is entered in the input that cannot be a valid hex address or ens address
|
||||||
|
- [ ] Support ens names. (enter dinodan.eth on mainnet) After entering the plain text address, the hex address should appear in the input with a green checkmark beside
|
||||||
|
- [ ] Should show a 'no such address' error if a non-existent ens address is entered
|
||||||
|
- [ ] **Amount row** _It should:_
|
||||||
|
- [ ] allow user to enter any rational number >= 0
|
||||||
|
- [ ] allow user to copy and paste into the field
|
||||||
|
- [ ] show an insufficient funds error if an amount > balance - gas fee
|
||||||
|
- [ ] display 'ETH' after the number amount. The position of 'ETH' should change as the length of the input amount text changes
|
||||||
|
- [ ] display the value of the amount of ETH in the current currency, formatted in that currency
|
||||||
|
- [ ] show a 'max' but if amount < balance - gas fee
|
||||||
|
- [ ] show no max button or error if amount === balance - gas fee
|
||||||
|
- [ ] set the amount to balance - gas fee if the 'max' button is clicked
|
||||||
|
- [ ] **Gas Fee Display row** _It should:_
|
||||||
|
- [ ] Default to the fee given by the estimated gas price
|
||||||
|
- [ ] display the fee in ETH and the current currency
|
||||||
|
- [ ] update when changes are made using the customize gas modal
|
||||||
|
- [ ] **Cancel button** _It should:_
|
||||||
|
- [ ] Take the user back to the main screen
|
||||||
|
- [ ] **submit button** _It should:_
|
||||||
|
- [ ] be disabled if no recipient address is provided or if any field is in error
|
||||||
|
- [ ] sign a transaction with the info in the above form, and display the details of that transaction on the confirm screen
|
||||||
|
|
||||||
|
## Send token mode
|
||||||
|
- [ ] **Header** _It should:_
|
||||||
|
- [ ] have title "Send Tokens"
|
||||||
|
- [ ] have sub title "Only send [token symbol] to an Ethereum address."
|
||||||
|
- [ ] return user to main screen when top right X is clicked
|
||||||
|
- [ ] **From row** _It should:_
|
||||||
|
- [ ] Behave the same as 'Send ETH mode' (see above)
|
||||||
|
- [ ] **To row** _It should:_
|
||||||
|
- [ ] Behave the same as 'Send ETH mode' (see above)
|
||||||
|
- [ ] **Amount row** _It should:_
|
||||||
|
- [ ] allow user to enter any rational number >= 0
|
||||||
|
- [ ] allow user to copy and paste into the field
|
||||||
|
- [ ] show an 'insufficient tokens' error if an amount > token balance
|
||||||
|
- [ ] show an 'insufficient funds' error if an gas fee > eth balance
|
||||||
|
- [ ] display [token symbol] after the number amount. The position of [token symbol] should change as the length of the input amount text changes
|
||||||
|
- [ ] display the value of the amount of tokens in the current currency, formatted in that currency
|
||||||
|
- [ ] show a 'max' but if amount < token balance
|
||||||
|
- [ ] show no max button or error if amount === token balance
|
||||||
|
- [ ] set the amount to token balance if the 'max' button is clicked
|
||||||
|
- [ ] **Gas Fee Display row** _It should:_
|
||||||
|
- [ ] Behave the same as 'Send ETH mode' (see above)
|
||||||
|
- [ ] **Cancel button** _It should:_
|
||||||
|
- [ ] Take the user back to the main screen
|
||||||
|
- [ ] **submit button** _It should:_
|
||||||
|
- [ ] be disabled if no recipient address is provided or if any field is in error
|
||||||
|
- [ ] sign a token transaction with the info in the above form, and display the details of that transaction on the confirm screen
|
||||||
|
|
||||||
|
## Edit send Eth mode
|
||||||
|
- [ ] Say 'Editing transaction' in the header
|
||||||
|
- [ ] display a button to go back to the confirmation screen without applying update
|
||||||
|
- [ ] say 'update transaction' on the submit button
|
||||||
|
- [ ] update the existing transaction, instead of signing a new one, when clicking the submit button
|
||||||
|
- [ ] Otherwise, behave the same as 'Send ETH mode' (see above)
|
||||||
|
|
||||||
|
## Edit send token mode
|
||||||
|
- [ ] Behave the same as 'Edit send Eth mode' (see above)
|
||||||
|
|
||||||
|
## Specific cases to test
|
||||||
|
- [ ] Send eth to a hex address
|
||||||
|
- [ ] Send eth to an ENS address
|
||||||
|
- [ ] Donate to the faucet at https://faucet.metamask.io/ and edit the transaction before confirming
|
||||||
|
- [ ] Send a token that is available on the 'Add Token' screen search to a hex address
|
||||||
|
- [ ] Create a custom token at https://tokenfactory.surge.sh/ and send it to a hex address
|
||||||
|
- [ ] Send a token to an ENS address
|
||||||
|
- [ ] Create a token transaction using https://tokenfactory.surge.sh/#/, and edit the transaction before confirming
|
||||||
|
- [ ] Send each of MKR, EOS and ICON using myetherwallet, and edit the transaction before confirming
|
@ -13,8 +13,13 @@ import {
|
|||||||
INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE,
|
INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE,
|
||||||
INITIALIZE_NOTICE_ROUTE,
|
INITIALIZE_NOTICE_ROUTE,
|
||||||
} from '../../../../ui/app/routes'
|
} from '../../../../ui/app/routes'
|
||||||
|
import TextField from '../../../../ui/app/components/text-field'
|
||||||
|
|
||||||
class CreatePasswordScreen extends Component {
|
class CreatePasswordScreen extends Component {
|
||||||
|
static contextTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
isLoading: PropTypes.bool.isRequired,
|
isLoading: PropTypes.bool.isRequired,
|
||||||
createAccount: PropTypes.func.isRequired,
|
createAccount: PropTypes.func.isRequired,
|
||||||
@ -27,6 +32,8 @@ class CreatePasswordScreen extends Component {
|
|||||||
state = {
|
state = {
|
||||||
password: '',
|
password: '',
|
||||||
confirmPassword: '',
|
confirmPassword: '',
|
||||||
|
passwordError: null,
|
||||||
|
confirmPasswordError: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@ -69,82 +76,37 @@ class CreatePasswordScreen extends Component {
|
|||||||
.then(() => history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE))
|
.then(() => history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE))
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFields () {
|
handlePasswordChange (password) {
|
||||||
const { isMascara, history } = this.props
|
const { confirmPassword } = this.state
|
||||||
|
let confirmPasswordError = null
|
||||||
|
let passwordError = null
|
||||||
|
|
||||||
return (
|
if (password && password.length < 8) {
|
||||||
<div className={classnames({ 'first-view-main-wrapper': !isMascara })}>
|
passwordError = this.context.t('passwordNotLongEnough')
|
||||||
<div className={classnames({
|
}
|
||||||
'first-view-main': !isMascara,
|
|
||||||
'first-view-main__mascara': isMascara,
|
if (confirmPassword && password !== confirmPassword) {
|
||||||
})}>
|
confirmPasswordError = this.context.t('passwordsDontMatch')
|
||||||
{isMascara && <div className="mascara-info first-view-phone-invisible">
|
}
|
||||||
<Mascot
|
|
||||||
animationEventEmitter={this.animationEventEmitter}
|
this.setState({ password, passwordError, confirmPasswordError })
|
||||||
width="225"
|
}
|
||||||
height="225"
|
|
||||||
/>
|
handleConfirmPasswordChange (confirmPassword) {
|
||||||
<div className="info">
|
const { password } = this.state
|
||||||
MetaMask is a secure identity vault for Ethereum.
|
let confirmPasswordError = null
|
||||||
</div>
|
|
||||||
<div className="info">
|
if (password !== confirmPassword) {
|
||||||
It allows you to hold ether & tokens, and interact with decentralized applications.
|
confirmPasswordError = this.context.t('passwordsDontMatch')
|
||||||
</div>
|
}
|
||||||
</div>}
|
|
||||||
<div className="create-password">
|
this.setState({ confirmPassword, confirmPasswordError })
|
||||||
<div className="create-password__title">
|
|
||||||
Create Password
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
className="first-time-flow__input"
|
|
||||||
type="password"
|
|
||||||
placeholder="New Password (min 8 characters)"
|
|
||||||
onChange={e => this.setState({password: e.target.value})}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
className="first-time-flow__input create-password__confirm-input"
|
|
||||||
type="password"
|
|
||||||
placeholder="Confirm Password"
|
|
||||||
onChange={e => this.setState({confirmPassword: e.target.value})}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className="first-time-flow__button"
|
|
||||||
disabled={!this.isValid()}
|
|
||||||
onClick={this.createAccount}
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</button>
|
|
||||||
<a
|
|
||||||
href=""
|
|
||||||
className="first-time-flow__link create-password__import-link"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault()
|
|
||||||
history.push(INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Import with seed phrase
|
|
||||||
</a>
|
|
||||||
{ /* }
|
|
||||||
<a
|
|
||||||
href=""
|
|
||||||
className="first-time-flow__link create-password__import-link"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault()
|
|
||||||
history.push(INITIALIZE_IMPORT_ACCOUNT_ROUTE)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Import an account
|
|
||||||
</a>
|
|
||||||
{ */ }
|
|
||||||
<Breadcrumbs total={3} currentIndex={0} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { history, isMascara } = this.props
|
const { history, isMascara } = this.props
|
||||||
|
const { passwordError, confirmPasswordError } = this.state
|
||||||
|
const { t } = this.context
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames({ 'first-view-main-wrapper': !isMascara })}>
|
<div className={classnames({ 'first-view-main-wrapper': !isMascara })}>
|
||||||
@ -169,17 +131,32 @@ class CreatePasswordScreen extends Component {
|
|||||||
<div className="create-password__title">
|
<div className="create-password__title">
|
||||||
Create Password
|
Create Password
|
||||||
</div>
|
</div>
|
||||||
<input
|
<TextField
|
||||||
|
id="create-password"
|
||||||
|
label={t('newPassword')}
|
||||||
|
type="password"
|
||||||
className="first-time-flow__input"
|
className="first-time-flow__input"
|
||||||
type="password"
|
value={this.state.password}
|
||||||
placeholder="New Password (min 8 characters)"
|
onChange={event => this.handlePasswordChange(event.target.value)}
|
||||||
onChange={e => this.setState({password: e.target.value})}
|
error={passwordError}
|
||||||
|
autoFocus
|
||||||
|
autoComplete="new-password"
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
largeLabel
|
||||||
/>
|
/>
|
||||||
<input
|
<TextField
|
||||||
className="first-time-flow__input create-password__confirm-input"
|
id="confirm-password"
|
||||||
|
label={t('confirmPassword')}
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Confirm Password"
|
className="first-time-flow__input"
|
||||||
onChange={e => this.setState({confirmPassword: e.target.value})}
|
value={this.state.confirmPassword}
|
||||||
|
onChange={event => this.handleConfirmPasswordChange(event.target.value)}
|
||||||
|
error={confirmPasswordError}
|
||||||
|
autoComplete="confirm-password"
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
largeLabel
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className="first-time-flow__button"
|
className="first-time-flow__button"
|
||||||
|
@ -70,10 +70,14 @@ class ImportAccountScreen extends Component {
|
|||||||
switch (this.state.selectedOption) {
|
switch (this.state.selectedOption) {
|
||||||
case OPTIONS.JSON_FILE:
|
case OPTIONS.JSON_FILE:
|
||||||
return importNewAccount('JSON File', [ jsonFile, password ])
|
return importNewAccount('JSON File', [ jsonFile, password ])
|
||||||
|
// JS runtime requires caught rejections but failures are handled by Redux
|
||||||
|
.catch()
|
||||||
.then(next)
|
.then(next)
|
||||||
case OPTIONS.PRIVATE_KEY:
|
case OPTIONS.PRIVATE_KEY:
|
||||||
default:
|
default:
|
||||||
return importNewAccount('Private Key', [ privateKey ])
|
return importNewAccount('Private Key', [ privateKey ])
|
||||||
|
// JS runtime requires caught rejections but failures are handled by Redux
|
||||||
|
.catch()
|
||||||
.then(next)
|
.then(next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,33 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
import classnames from 'classnames'
|
|
||||||
import {
|
import {
|
||||||
createNewVaultAndRestore,
|
createNewVaultAndRestore,
|
||||||
hideWarning,
|
|
||||||
displayWarning,
|
|
||||||
unMarkPasswordForgotten,
|
unMarkPasswordForgotten,
|
||||||
} from '../../../../ui/app/actions'
|
} from '../../../../ui/app/actions'
|
||||||
import { DEFAULT_ROUTE, INITIALIZE_NOTICE_ROUTE } from '../../../../ui/app/routes'
|
import { INITIALIZE_NOTICE_ROUTE } from '../../../../ui/app/routes'
|
||||||
|
import TextField from '../../../../ui/app/components/text-field'
|
||||||
|
|
||||||
class ImportSeedPhraseScreen extends Component {
|
class ImportSeedPhraseScreen extends Component {
|
||||||
|
static contextTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
warning: PropTypes.string,
|
warning: PropTypes.string,
|
||||||
createNewVaultAndRestore: PropTypes.func.isRequired,
|
createNewVaultAndRestore: PropTypes.func.isRequired,
|
||||||
hideWarning: PropTypes.func.isRequired,
|
|
||||||
displayWarning: PropTypes.func,
|
|
||||||
leaveImportSeedScreenState: PropTypes.func,
|
leaveImportSeedScreenState: PropTypes.func,
|
||||||
history: PropTypes.object,
|
history: PropTypes.object,
|
||||||
|
isLoading: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
seedPhrase: '',
|
seedPhrase: '',
|
||||||
password: '',
|
password: '',
|
||||||
confirmPassword: '',
|
confirmPassword: '',
|
||||||
|
seedPhraseError: null,
|
||||||
|
passwordError: null,
|
||||||
|
confirmPasswordError: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
parseSeedPhrase = (seedPhrase) => {
|
parseSeedPhrase = (seedPhrase) => {
|
||||||
@ -32,39 +36,47 @@ class ImportSeedPhraseScreen extends Component {
|
|||||||
.join(' ')
|
.join(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = ({ seedPhrase, password, confirmPassword }) => {
|
handleSeedPhraseChange (seedPhrase) {
|
||||||
const {
|
let seedPhraseError = null
|
||||||
password: prevPassword,
|
|
||||||
confirmPassword: prevConfirmPassword,
|
|
||||||
} = this.state
|
|
||||||
const { displayWarning, hideWarning } = this.props
|
|
||||||
|
|
||||||
let warning = null
|
|
||||||
|
|
||||||
if (seedPhrase && this.parseSeedPhrase(seedPhrase).split(' ').length !== 12) {
|
if (seedPhrase && this.parseSeedPhrase(seedPhrase).split(' ').length !== 12) {
|
||||||
warning = 'Seed Phrases are 12 words long'
|
seedPhraseError = this.context.t('seedPhraseReq')
|
||||||
} else if (password && password.length < 8) {
|
|
||||||
warning = 'Passwords require a mimimum length of 8'
|
|
||||||
} else if ((password || prevPassword) !== (confirmPassword || prevConfirmPassword)) {
|
|
||||||
warning = 'Confirmed password does not match'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (warning) {
|
this.setState({ seedPhrase, seedPhraseError })
|
||||||
displayWarning(warning)
|
}
|
||||||
} else {
|
|
||||||
hideWarning()
|
handlePasswordChange (password) {
|
||||||
|
const { confirmPassword } = this.state
|
||||||
|
let confirmPasswordError = null
|
||||||
|
let passwordError = null
|
||||||
|
|
||||||
|
if (password && password.length < 8) {
|
||||||
|
passwordError = this.context.t('passwordNotLongEnough')
|
||||||
}
|
}
|
||||||
|
|
||||||
seedPhrase && this.setState({ seedPhrase })
|
if (confirmPassword && password !== confirmPassword) {
|
||||||
password && this.setState({ password })
|
confirmPasswordError = this.context.t('passwordsDontMatch')
|
||||||
confirmPassword && this.setState({ confirmPassword })
|
}
|
||||||
|
|
||||||
|
this.setState({ password, passwordError, confirmPasswordError })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleConfirmPasswordChange (confirmPassword) {
|
||||||
|
const { password } = this.state
|
||||||
|
let confirmPasswordError = null
|
||||||
|
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
confirmPasswordError = this.context.t('passwordsDontMatch')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ confirmPassword, confirmPasswordError })
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick = () => {
|
onClick = () => {
|
||||||
const { password, seedPhrase } = this.state
|
const { password, seedPhrase } = this.state
|
||||||
const {
|
const {
|
||||||
createNewVaultAndRestore,
|
createNewVaultAndRestore,
|
||||||
displayWarning,
|
|
||||||
leaveImportSeedScreenState,
|
leaveImportSeedScreenState,
|
||||||
history,
|
history,
|
||||||
} = this.props
|
} = this.props
|
||||||
@ -74,10 +86,23 @@ class ImportSeedPhraseScreen extends Component {
|
|||||||
.then(() => history.push(INITIALIZE_NOTICE_ROUTE))
|
.then(() => history.push(INITIALIZE_NOTICE_ROUTE))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasError () {
|
||||||
|
const { passwordError, confirmPasswordError, seedPhraseError } = this.state
|
||||||
|
return passwordError || confirmPasswordError || seedPhraseError
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { seedPhrase, password, confirmPassword } = this.state
|
const {
|
||||||
const { warning, isLoading } = this.props
|
seedPhrase,
|
||||||
const importDisabled = warning || !seedPhrase || !password || !confirmPassword || isLoading
|
password,
|
||||||
|
confirmPassword,
|
||||||
|
seedPhraseError,
|
||||||
|
passwordError,
|
||||||
|
confirmPasswordError,
|
||||||
|
} = this.state
|
||||||
|
const { t } = this.context
|
||||||
|
const { isLoading } = this.props
|
||||||
|
const disabled = !seedPhrase || !password || !confirmPassword || isLoading || this.hasError()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="first-view-main-wrapper">
|
<div className="first-view-main-wrapper">
|
||||||
@ -103,45 +128,42 @@ class ImportSeedPhraseScreen extends Component {
|
|||||||
<label className="import-account__input-label">Wallet Seed</label>
|
<label className="import-account__input-label">Wallet Seed</label>
|
||||||
<textarea
|
<textarea
|
||||||
className="import-account__secret-phrase"
|
className="import-account__secret-phrase"
|
||||||
onChange={e => this.onChange({seedPhrase: e.target.value})}
|
onChange={e => this.handleSeedPhraseChange(e.target.value)}
|
||||||
value={this.state.seedPhrase}
|
value={this.state.seedPhrase}
|
||||||
placeholder="Separate each word with a single space"
|
placeholder="Separate each word with a single space"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span className="error">
|
||||||
className="error"
|
{ seedPhraseError }
|
||||||
>
|
|
||||||
{this.props.warning}
|
|
||||||
</span>
|
</span>
|
||||||
<div className="import-account__input-wrapper">
|
<TextField
|
||||||
<label className="import-account__input-label">New Password</label>
|
id="password"
|
||||||
<input
|
label={t('newPassword')}
|
||||||
className="first-time-flow__input"
|
type="password"
|
||||||
type="password"
|
className="first-time-flow__input"
|
||||||
placeholder="New Password (min 8 characters)"
|
value={this.state.password}
|
||||||
onChange={e => this.onChange({password: e.target.value})}
|
onChange={event => this.handlePasswordChange(event.target.value)}
|
||||||
/>
|
error={passwordError}
|
||||||
</div>
|
autoComplete="new-password"
|
||||||
<div className="import-account__input-wrapper">
|
margin="normal"
|
||||||
<label
|
largeLabel
|
||||||
className={classnames('import-account__input-label', {
|
/>
|
||||||
'import-account__input-label__disabled': password.length < 8,
|
<TextField
|
||||||
})}
|
id="confirm-password"
|
||||||
>Confirm Password</label>
|
label={t('confirmPassword')}
|
||||||
<input
|
type="password"
|
||||||
className={classnames('first-time-flow__input', {
|
className="first-time-flow__input"
|
||||||
'first-time-flow__input__disabled': password.length < 8,
|
value={this.state.confirmPassword}
|
||||||
})}
|
onChange={event => this.handleConfirmPasswordChange(event.target.value)}
|
||||||
type="password"
|
error={confirmPasswordError}
|
||||||
placeholder="Confirm Password"
|
autoComplete="confirm-password"
|
||||||
onChange={e => this.onChange({confirmPassword: e.target.value})}
|
margin="normal"
|
||||||
disabled={password.length < 8}
|
largeLabel
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
className="first-time-flow__button"
|
className="first-time-flow__button"
|
||||||
onClick={() => !importDisabled && this.onClick()}
|
onClick={() => !disabled && this.onClick()}
|
||||||
disabled={importDisabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
Import
|
Import
|
||||||
</button>
|
</button>
|
||||||
@ -159,7 +181,5 @@ export default connect(
|
|||||||
dispatch(unMarkPasswordForgotten())
|
dispatch(unMarkPasswordForgotten())
|
||||||
},
|
},
|
||||||
createNewVaultAndRestore: (pw, seed) => dispatch(createNewVaultAndRestore(pw, seed)),
|
createNewVaultAndRestore: (pw, seed) => dispatch(createNewVaultAndRestore(pw, seed)),
|
||||||
displayWarning: (warning) => dispatch(displayWarning(warning)),
|
|
||||||
hideWarning: () => dispatch(hideWarning()),
|
|
||||||
})
|
})
|
||||||
)(ImportSeedPhraseScreen)
|
)(ImportSeedPhraseScreen)
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: #f7861c;
|
background: #f7861c;
|
||||||
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alpha-warning,
|
.alpha-warning,
|
||||||
@ -173,10 +174,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.first-time-flow__input {
|
.first-time-flow__input {
|
||||||
width: initial !important;
|
width: 100%;
|
||||||
font-size: 14px !important;
|
|
||||||
line-height: 18px !important;
|
|
||||||
padding: 12px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tou__body {
|
.tou__body {
|
||||||
@ -247,7 +245,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.create-password__confirm-input {
|
.create-password__confirm-input {
|
||||||
margin-top: 15px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-password__import-link {
|
.create-password__import-link {
|
||||||
@ -519,10 +517,6 @@ button.backup-phrase__confirm-seed-option:hover {
|
|||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.first-time-flow__input--error {
|
|
||||||
border: 1px solid #FF001F !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.import-account__input-error-message {
|
.import-account__input-error-message {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
width: 422px;
|
width: 422px;
|
||||||
@ -543,7 +537,13 @@ button.backup-phrase__confirm-seed-option:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.import-account__input {
|
.import-account__input {
|
||||||
width: 325px !important;
|
width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 575px) {
|
||||||
|
.import-account__input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.import-account__file-input {
|
.import-account__file-input {
|
||||||
@ -680,20 +680,6 @@ button.backup-phrase__confirm-seed-option:hover {
|
|||||||
|
|
||||||
.first-time-flow__input {
|
.first-time-flow__input {
|
||||||
width: 350px;
|
width: 350px;
|
||||||
font-size: 18px;
|
|
||||||
line-height: 24px;
|
|
||||||
padding: 15px;
|
|
||||||
border: 1px solid #CDCDCD;
|
|
||||||
background-color: #FFFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.first-time-flow__input__disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.first-time-flow__input::placeholder {
|
|
||||||
color: #9B9B9B;
|
|
||||||
font-weight: 200;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.first-time-flow__button {
|
.first-time-flow__button {
|
||||||
|
@ -3,6 +3,8 @@ import PropTypes from 'prop-types'
|
|||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
import { withRouter, Switch, Route } from 'react-router-dom'
|
import { withRouter, Switch, Route } from 'react-router-dom'
|
||||||
import { compose } from 'recompose'
|
import { compose } from 'recompose'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
|
||||||
import CreatePasswordScreen from './create-password-screen'
|
import CreatePasswordScreen from './create-password-screen'
|
||||||
import UniqueImageScreen from './unique-image-screen'
|
import UniqueImageScreen from './unique-image-screen'
|
||||||
import NoticeScreen from './notice-screen'
|
import NoticeScreen from './notice-screen'
|
||||||
@ -33,6 +35,7 @@ class FirstTimeFlow extends Component {
|
|||||||
isUnlocked: PropTypes.bool,
|
isUnlocked: PropTypes.bool,
|
||||||
history: PropTypes.object,
|
history: PropTypes.object,
|
||||||
welcomeScreenSeen: PropTypes.bool,
|
welcomeScreenSeen: PropTypes.bool,
|
||||||
|
isPopup: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -41,23 +44,44 @@ class FirstTimeFlow extends Component {
|
|||||||
noActiveNotices: false,
|
noActiveNotices: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
renderAppBar () {
|
||||||
|
const { welcomeScreenSeen } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="first-time-flow">
|
<div className="alpha-warning__container">
|
||||||
<Switch>
|
<h2 className={classnames({
|
||||||
<Route exact path={INITIALIZE_IMPORT_ACCOUNT_ROUTE} component={ImportAccountScreen} />
|
'alpha-warning': welcomeScreenSeen,
|
||||||
<Route
|
'alpha-warning-welcome-screen': !welcomeScreenSeen,
|
||||||
exact
|
})}
|
||||||
path={INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE}
|
>
|
||||||
component={ImportSeedPhraseScreen}
|
Please be aware that this version is still under development
|
||||||
/>
|
</h2>
|
||||||
<Route exact path={INITIALIZE_UNIQUE_IMAGE_ROUTE} component={UniqueImageScreen} />
|
</div>
|
||||||
<Route exact path={INITIALIZE_NOTICE_ROUTE} component={NoticeScreen} />
|
)
|
||||||
<Route exact path={INITIALIZE_BACKUP_PHRASE_ROUTE} component={BackupPhraseScreen} />
|
}
|
||||||
<Route exact path={INITIALIZE_CONFIRM_SEED_ROUTE} component={ConfirmSeed} />
|
|
||||||
<Route exact path={INITIALIZE_CREATE_PASSWORD_ROUTE} component={CreatePasswordScreen} />
|
render () {
|
||||||
<Route exact path={INITIALIZE_ROUTE} component={WelcomeScreen} />
|
const { isPopup } = this.props
|
||||||
</Switch>
|
|
||||||
|
return (
|
||||||
|
<div className="flex-column flex-grow">
|
||||||
|
{ !isPopup && this.renderAppBar() }
|
||||||
|
<div className="first-time-flow">
|
||||||
|
<Switch>
|
||||||
|
<Route exact path={INITIALIZE_IMPORT_ACCOUNT_ROUTE} component={ImportAccountScreen} />
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE}
|
||||||
|
component={ImportSeedPhraseScreen}
|
||||||
|
/>
|
||||||
|
<Route exact path={INITIALIZE_UNIQUE_IMAGE_ROUTE} component={UniqueImageScreen} />
|
||||||
|
<Route exact path={INITIALIZE_NOTICE_ROUTE} component={NoticeScreen} />
|
||||||
|
<Route exact path={INITIALIZE_BACKUP_PHRASE_ROUTE} component={BackupPhraseScreen} />
|
||||||
|
<Route exact path={INITIALIZE_CONFIRM_SEED_ROUTE} component={ConfirmSeed} />
|
||||||
|
<Route exact path={INITIALIZE_CREATE_PASSWORD_ROUTE} component={CreatePasswordScreen} />
|
||||||
|
<Route exact path={INITIALIZE_ROUTE} component={WelcomeScreen} />
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -73,6 +97,7 @@ const mapStateToProps = ({ metamask }) => {
|
|||||||
isMascara,
|
isMascara,
|
||||||
isUnlocked,
|
isUnlocked,
|
||||||
welcomeScreenSeen,
|
welcomeScreenSeen,
|
||||||
|
isPopup,
|
||||||
} = metamask
|
} = metamask
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -84,6 +109,7 @@ const mapStateToProps = ({ metamask }) => {
|
|||||||
forgottenPassword,
|
forgottenPassword,
|
||||||
isUnlocked,
|
isUnlocked,
|
||||||
welcomeScreenSeen,
|
welcomeScreenSeen,
|
||||||
|
isPopup,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ AccountDetailScreen.prototype.render = function () {
|
|||||||
isEditingLabel: false,
|
isEditingLabel: false,
|
||||||
},
|
},
|
||||||
saveText: (text) => {
|
saveText: (text) => {
|
||||||
props.dispatch(actions.saveAccountLabel(selected, text))
|
props.dispatch(actions.setAccountLabel(selected, text))
|
||||||
},
|
},
|
||||||
}, [
|
}, [
|
||||||
|
|
||||||
|
@ -96,6 +96,8 @@ class JsonImportSubview extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.props.importNewAccount([ fileContents, password ])
|
this.props.importNewAccount([ fileContents, password ])
|
||||||
|
// JS runtime requires caught rejections but failures are handled by Redux
|
||||||
|
.catch()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,4 +64,6 @@ PrivateKeyImportView.prototype.createNewKeychain = function () {
|
|||||||
const input = document.getElementById('private-key-box')
|
const input = document.getElementById('private-key-box')
|
||||||
const privateKey = input.value
|
const privateKey = input.value
|
||||||
this.props.dispatch(actions.importNewAccount('Private Key', [ privateKey ]))
|
this.props.dispatch(actions.importNewAccount('Private Key', [ privateKey ]))
|
||||||
|
// JS runtime requires caught rejections but failures are handled by Redux
|
||||||
|
.catch()
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,6 @@ const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
|
|||||||
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
|
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
|
||||||
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
|
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
|
||||||
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
|
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
|
||||||
const { BETA_UI_NETWORK_TYPE } = require('../../app/scripts/controllers/network/enums')
|
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(App)
|
module.exports = connect(mapStateToProps)(App)
|
||||||
|
|
||||||
@ -63,7 +62,7 @@ function mapStateToProps (state) {
|
|||||||
isInitialized: state.metamask.isInitialized,
|
isInitialized: state.metamask.isInitialized,
|
||||||
isUnlocked: state.metamask.isUnlocked,
|
isUnlocked: state.metamask.isUnlocked,
|
||||||
currentView: state.appState.currentView,
|
currentView: state.appState.currentView,
|
||||||
activeAddress: state.appState.activeAddress,
|
selectedAddress: state.metamask.selectedAddress,
|
||||||
transForward: state.appState.transForward,
|
transForward: state.appState.transForward,
|
||||||
isMascara: state.metamask.isMascara,
|
isMascara: state.metamask.isMascara,
|
||||||
isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized),
|
isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized),
|
||||||
@ -198,7 +197,7 @@ App.prototype.renderAppBar = function () {
|
|||||||
style: {},
|
style: {},
|
||||||
enableAccountsSelector: true,
|
enableAccountsSelector: true,
|
||||||
identities: this.props.identities,
|
identities: this.props.identities,
|
||||||
selected: this.props.currentView.context,
|
selected: this.props.selectedAddress,
|
||||||
network: this.props.network,
|
network: this.props.network,
|
||||||
keyrings: this.props.keyrings,
|
keyrings: this.props.keyrings,
|
||||||
}, []),
|
}, []),
|
||||||
@ -409,7 +408,6 @@ App.prototype.renderDropdown = function () {
|
|||||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
this.props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
|
this.props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
|
||||||
.then(() => this.props.dispatch(actions.setNetworkEndpoints(BETA_UI_NETWORK_TYPE)))
|
|
||||||
},
|
},
|
||||||
}, 'Try Beta!'),
|
}, 'Try Beta!'),
|
||||||
])
|
])
|
||||||
@ -472,7 +470,6 @@ App.prototype.renderPrimary = function () {
|
|||||||
onClick: () => {
|
onClick: () => {
|
||||||
global.platform.openExtensionInBrowser()
|
global.platform.openExtensionInBrowser()
|
||||||
props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
|
props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
|
||||||
.then(() => props.dispatch(actions.setNetworkEndpoints(BETA_UI_NETWORK_TYPE)))
|
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
fontSize: '0.8em',
|
fontSize: '0.8em',
|
||||||
@ -591,7 +588,7 @@ App.prototype.renderPrimary = function () {
|
|||||||
},
|
},
|
||||||
}, [
|
}, [
|
||||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
|
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
|
||||||
onClick: () => props.dispatch(actions.backToAccountDetail(props.activeAddress)),
|
onClick: () => props.dispatch(actions.backToAccountDetail(props.selectedAddress)),
|
||||||
style: {
|
style: {
|
||||||
marginLeft: '10px',
|
marginLeft: '10px',
|
||||||
marginTop: '50px',
|
marginTop: '50px',
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
const Component = require('react').Component
|
const Component = require('react').Component
|
||||||
const h = require('react-hyperscript')
|
const h = require('react-hyperscript')
|
||||||
const inherits = require('util').inherits
|
const inherits = require('util').inherits
|
||||||
const extend = require('xtend')
|
|
||||||
const debounce = require('debounce')
|
const debounce = require('debounce')
|
||||||
const copyToClipboard = require('copy-to-clipboard')
|
const copyToClipboard = require('copy-to-clipboard')
|
||||||
const ENS = require('ethjs-ens')
|
const ENS = require('ethjs-ens')
|
||||||
@ -20,55 +19,61 @@ function EnsInput () {
|
|||||||
|
|
||||||
EnsInput.prototype.render = function () {
|
EnsInput.prototype.render = function () {
|
||||||
const props = this.props
|
const props = this.props
|
||||||
const opts = extend(props, {
|
|
||||||
list: 'addresses',
|
|
||||||
onChange: () => {
|
|
||||||
const network = this.props.network
|
|
||||||
const networkHasEnsSupport = getNetworkEnsSupport(network)
|
|
||||||
if (!networkHasEnsSupport) return
|
|
||||||
|
|
||||||
const recipient = document.querySelector('input[name="address"]').value
|
function onInputChange() {
|
||||||
if (recipient.match(ensRE) === null) {
|
const network = this.props.network
|
||||||
return this.setState({
|
const networkHasEnsSupport = getNetworkEnsSupport(network)
|
||||||
loadingEns: false,
|
if (!networkHasEnsSupport) return
|
||||||
ensResolution: null,
|
|
||||||
ensFailure: null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
const recipient = document.querySelector('input[name="address"]').value
|
||||||
loadingEns: true,
|
if (recipient.match(ensRE) === null) {
|
||||||
|
return this.setState({
|
||||||
|
loadingEns: false,
|
||||||
|
ensResolution: null,
|
||||||
|
ensFailure: null,
|
||||||
})
|
})
|
||||||
this.checkName()
|
}
|
||||||
},
|
|
||||||
})
|
this.setState({
|
||||||
return h('div', {
|
loadingEns: true,
|
||||||
style: { width: '100%' },
|
})
|
||||||
}, [
|
this.checkName()
|
||||||
h('input.large-input', opts),
|
}
|
||||||
// The address book functionality.
|
|
||||||
h('datalist#addresses',
|
return (
|
||||||
[
|
h('div', {
|
||||||
// Corresponds to the addresses owned.
|
style: { width: '100%' },
|
||||||
Object.keys(props.identities).map((key) => {
|
}, [
|
||||||
const identity = props.identities[key]
|
h('input.large-input', {
|
||||||
return h('option', {
|
name: props.name,
|
||||||
value: identity.address,
|
placeholder: props.placeholder,
|
||||||
label: identity.name,
|
list: 'addresses',
|
||||||
key: identity.address,
|
onChange: onInputChange.bind(this),
|
||||||
})
|
}),
|
||||||
}),
|
// The address book functionality.
|
||||||
// Corresponds to previously sent-to addresses.
|
h('datalist#addresses',
|
||||||
props.addressBook.map((identity) => {
|
[
|
||||||
return h('option', {
|
// Corresponds to the addresses owned.
|
||||||
value: identity.address,
|
Object.keys(props.identities).map((key) => {
|
||||||
label: identity.name,
|
const identity = props.identities[key]
|
||||||
key: identity.address,
|
return h('option', {
|
||||||
})
|
value: identity.address,
|
||||||
}),
|
label: identity.name,
|
||||||
]),
|
key: identity.address,
|
||||||
this.ensIcon(),
|
})
|
||||||
])
|
}),
|
||||||
|
// Corresponds to previously sent-to addresses.
|
||||||
|
props.addressBook.map((identity) => {
|
||||||
|
return h('option', {
|
||||||
|
value: identity.address,
|
||||||
|
label: identity.name,
|
||||||
|
key: identity.address,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
this.ensIcon(),
|
||||||
|
])
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsInput.prototype.componentDidMount = function () {
|
EnsInput.prototype.componentDidMount = function () {
|
||||||
|
7160
package-lock.json
generated
7160
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
43
package.json
43
package.json
@ -9,12 +9,16 @@
|
|||||||
"dist": "gulp dist",
|
"dist": "gulp dist",
|
||||||
"doc": "jsdoc -c development/tools/.jsdoc.json",
|
"doc": "jsdoc -c development/tools/.jsdoc.json",
|
||||||
"test": "npm run test:unit && npm run test:integration && npm run lint",
|
"test": "npm run test:unit && npm run test:integration && npm run lint",
|
||||||
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\"",
|
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" && dot-only-hunter",
|
||||||
"test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js",
|
"test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js",
|
||||||
"test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara",
|
"test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara",
|
||||||
"test:integration:build": "gulp build:scss",
|
"test:integration:build": "gulp build:scss",
|
||||||
"test:e2e": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:e2e:run'",
|
"test:e2e:chrome": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:e2e:run:chrome'",
|
||||||
"test:e2e:run": "mocha test/e2e/metamask.spec --recursive",
|
"test:e2e:chrome:beta": "SELENIUM_BROWSER=chrome test/e2e/beta/run-all.sh",
|
||||||
|
"test:e2e:firefox": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:e2e:run:firefox'",
|
||||||
|
"test:e2e:firefox:beta": "SELENIUM_BROWSER=firefox test/e2e/beta/run-all.sh",
|
||||||
|
"test:e2e:run:chrome": "SELENIUM_BROWSER=chrome 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 npm run test:unit && npm run test:coveralls-upload",
|
||||||
@ -42,7 +46,8 @@
|
|||||||
"announce": "node development/announcer.js",
|
"announce": "node development/announcer.js",
|
||||||
"version:bump": "node development/run-version-bump.js",
|
"version:bump": "node development/run-version-bump.js",
|
||||||
"generateNotice": "node notices/notice-generator.js",
|
"generateNotice": "node notices/notice-generator.js",
|
||||||
"deleteNotice": "node notices/notice-delete.js"
|
"deleteNotice": "node notices/notice-delete.js",
|
||||||
|
"storybook": "start-storybook -p 6006 -c .storybook"
|
||||||
},
|
},
|
||||||
"browserify": {
|
"browserify": {
|
||||||
"transform": [
|
"transform": [
|
||||||
@ -60,6 +65,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@material-ui/core": "^1.0.0",
|
||||||
"abi-decoder": "^1.0.9",
|
"abi-decoder": "^1.0.9",
|
||||||
"asmcrypto.js": "0.22.0",
|
"asmcrypto.js": "0.22.0",
|
||||||
"async": "^2.5.0",
|
"async": "^2.5.0",
|
||||||
@ -76,10 +82,11 @@
|
|||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"clone": "^2.1.1",
|
"clone": "^2.1.1",
|
||||||
"copy-to-clipboard": "^3.0.8",
|
"copy-to-clipboard": "^3.0.8",
|
||||||
|
"css-loader": "^0.28.11",
|
||||||
"currency-formatter": "^1.4.2",
|
"currency-formatter": "^1.4.2",
|
||||||
"debounce": "^1.0.0",
|
"debounce": "^1.0.0",
|
||||||
"debounce-stream": "^2.0.0",
|
"debounce-stream": "^2.0.0",
|
||||||
"deep-extend": "^0.5.0",
|
"deep-extend": "^0.5.1",
|
||||||
"detect-node": "^2.0.3",
|
"detect-node": "^2.0.3",
|
||||||
"disc": "^1.3.2",
|
"disc": "^1.3.2",
|
||||||
"dnode": "^1.2.2",
|
"dnode": "^1.2.2",
|
||||||
@ -87,12 +94,10 @@
|
|||||||
"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": "^2.3.0",
|
|
||||||
"eth-contract-metadata": "^1.1.5",
|
"eth-contract-metadata": "^1.1.5",
|
||||||
"eth-hd-keyring": "^1.2.1",
|
"eth-hd-keyring": "^1.2.1",
|
||||||
"eth-json-rpc-filters": "^1.2.6",
|
"eth-json-rpc-filters": "^1.2.6",
|
||||||
"eth-json-rpc-infura": "^3.0.0",
|
"eth-json-rpc-infura": "^3.0.0",
|
||||||
"eth-keyring-controller": "^2.2.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": "^1.4.2",
|
||||||
@ -102,15 +107,16 @@
|
|||||||
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
|
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
|
||||||
"ethereumjs-wallet": "^0.6.0",
|
"ethereumjs-wallet": "^0.6.0",
|
||||||
"etherscan-link": "^1.0.2",
|
"etherscan-link": "^1.0.2",
|
||||||
"ethjs": "^0.3.4",
|
"ethjs": "^0.4.0",
|
||||||
"ethjs-contract": "^0.2.0",
|
"ethjs-contract": "^0.2.0",
|
||||||
"ethjs-ens": "^2.0.0",
|
"ethjs-ens": "^2.0.0",
|
||||||
"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",
|
||||||
"extensionizer": "^1.0.0",
|
"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",
|
||||||
|
"file-loader": "^1.1.11",
|
||||||
"fuse.js": "^3.2.0",
|
"fuse.js": "^3.2.0",
|
||||||
"gulp": "github:gulpjs/gulp#4.0",
|
"gulp": "github:gulpjs/gulp#4.0",
|
||||||
"gulp-autoprefixer": "^5.0.0",
|
"gulp-autoprefixer": "^5.0.0",
|
||||||
@ -181,7 +187,6 @@
|
|||||||
"sw-controller": "^1.0.3",
|
"sw-controller": "^1.0.3",
|
||||||
"sw-stream": "^2.0.2",
|
"sw-stream": "^2.0.2",
|
||||||
"textarea-caret": "^3.0.1",
|
"textarea-caret": "^3.0.1",
|
||||||
"through2": "^2.0.3",
|
|
||||||
"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",
|
||||||
@ -191,6 +196,9 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sentry/cli": "^1.30.3",
|
"@sentry/cli": "^1.30.3",
|
||||||
|
"@storybook/addon-info": "^3.4.2",
|
||||||
|
"@storybook/addon-knobs": "^3.4.2",
|
||||||
|
"@storybook/react": "^3.4.2",
|
||||||
"babel-core": "^6.24.1",
|
"babel-core": "^6.24.1",
|
||||||
"babel-eslint": "^8.0.0",
|
"babel-eslint": "^8.0.0",
|
||||||
"babel-plugin-transform-async-to-generator": "^6.24.1",
|
"babel-plugin-transform-async-to-generator": "^6.24.1",
|
||||||
@ -204,12 +212,15 @@
|
|||||||
"brfs": "^1.4.3",
|
"brfs": "^1.4.3",
|
||||||
"browserify": "^16.1.1",
|
"browserify": "^16.1.1",
|
||||||
"chai": "^4.1.0",
|
"chai": "^4.1.0",
|
||||||
"chromedriver": "^2.34.1",
|
"chromedriver": "2.36.0",
|
||||||
|
"clipboardy": "^1.2.3",
|
||||||
"compression": "^1.7.1",
|
"compression": "^1.7.1",
|
||||||
"coveralls": "^3.0.0",
|
"coveralls": "^3.0.0",
|
||||||
"cross-env": "^5.1.4",
|
"cross-env": "^5.1.4",
|
||||||
|
"css-loader": "^0.28.11",
|
||||||
"deep-freeze-strict": "^1.1.1",
|
"deep-freeze-strict": "^1.1.1",
|
||||||
"del": "^3.0.0",
|
"del": "^3.0.0",
|
||||||
|
"dot-only-hunter": "^1.0.3",
|
||||||
"envify": "^4.0.0",
|
"envify": "^4.0.0",
|
||||||
"enzyme": "^3.3.0",
|
"enzyme": "^3.3.0",
|
||||||
"enzyme-adapter-react-15": "^1.0.5",
|
"enzyme-adapter-react-15": "^1.0.5",
|
||||||
@ -218,9 +229,13 @@
|
|||||||
"eslint-plugin-mocha": "^5.0.0",
|
"eslint-plugin-mocha": "^5.0.0",
|
||||||
"eslint-plugin-react": "^7.4.0",
|
"eslint-plugin-react": "^7.4.0",
|
||||||
"eth-json-rpc-middleware": "^1.6.0",
|
"eth-json-rpc-middleware": "^1.6.0",
|
||||||
|
"eth-keyring-controller": "^3.1.4",
|
||||||
|
"file-loader": "^1.1.11",
|
||||||
"fs-promise": "^2.0.3",
|
"fs-promise": "^2.0.3",
|
||||||
"ganache-cli": "^6.1.0",
|
"ganache-cli": "^6.1.0",
|
||||||
"ganache-core": "^2.1.0",
|
"ganache-core": "^2.1.0",
|
||||||
|
"geckodriver": "^1.11.0",
|
||||||
|
"gh-pages": "^1.1.0",
|
||||||
"gifencoder": "^1.1.0",
|
"gifencoder": "^1.1.0",
|
||||||
"gulp": "github:gulpjs/gulp#6d71a658c61edb3090221579d8f97dbe086ba2ed",
|
"gulp": "github:gulpjs/gulp#6d71a658c61edb3090221579d8f97dbe086ba2ed",
|
||||||
"gulp-babel": "^7.0.0",
|
"gulp-babel": "^7.0.0",
|
||||||
@ -255,8 +270,10 @@
|
|||||||
"mocha-sinon": "^2.0.0",
|
"mocha-sinon": "^2.0.0",
|
||||||
"nock": "^9.0.14",
|
"nock": "^9.0.14",
|
||||||
"node-sass": "^4.7.2",
|
"node-sass": "^4.7.2",
|
||||||
|
"nsp": "^3.2.1",
|
||||||
"nyc": "^11.0.3",
|
"nyc": "^11.0.3",
|
||||||
"open": "0.0.5",
|
"open": "0.0.5",
|
||||||
|
"path": "^0.12.7",
|
||||||
"png-file-stream": "^1.0.0",
|
"png-file-stream": "^1.0.0",
|
||||||
"prompt": "^1.0.0",
|
"prompt": "^1.0.0",
|
||||||
"qs": "^6.2.0",
|
"qs": "^6.2.0",
|
||||||
@ -266,14 +283,18 @@
|
|||||||
"react-test-renderer": "^15.6.2",
|
"react-test-renderer": "^15.6.2",
|
||||||
"react-testutils-additions": "^15.2.0",
|
"react-testutils-additions": "^15.2.0",
|
||||||
"redux-test-utils": "^0.2.2",
|
"redux-test-utils": "^0.2.2",
|
||||||
|
"resolve-url-loader": "^2.3.0",
|
||||||
"rimraf": "^2.6.2",
|
"rimraf": "^2.6.2",
|
||||||
|
"sass-loader": "^7.0.1",
|
||||||
"selenium-webdriver": "^3.5.0",
|
"selenium-webdriver": "^3.5.0",
|
||||||
"shell-parallel": "^1.0.3",
|
"shell-parallel": "^1.0.3",
|
||||||
"sinon": "^5.0.0",
|
"sinon": "^5.0.0",
|
||||||
"source-map": "^0.7.2",
|
"source-map": "^0.7.2",
|
||||||
|
"style-loader": "^0.21.0",
|
||||||
"stylelint-config-standard": "^18.2.0",
|
"stylelint-config-standard": "^18.2.0",
|
||||||
"tape": "^4.5.1",
|
"tape": "^4.5.1",
|
||||||
"testem": "^2.0.0",
|
"testem": "^2.0.0",
|
||||||
|
"through2": "^2.0.3",
|
||||||
"vinyl-buffer": "^1.0.1",
|
"vinyl-buffer": "^1.0.1",
|
||||||
"vinyl-source-stream": "^2.0.0",
|
"vinyl-source-stream": "^2.0.0",
|
||||||
"watchify": "^3.9.0"
|
"watchify": "^3.9.0"
|
||||||
|
@ -6,6 +6,9 @@ module.exports = function(config) {
|
|||||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||||
basePath: process.cwd(),
|
basePath: process.cwd(),
|
||||||
|
|
||||||
|
// Uncomment to allow for longer timeouts
|
||||||
|
// browserNoActivityTimeout: 100000000,
|
||||||
|
|
||||||
browserConsoleLogOptions: {
|
browserConsoleLogOptions: {
|
||||||
terminal: false,
|
terminal: false,
|
||||||
},
|
},
|
||||||
|
406
test/e2e/beta/from-import-beta-ui.spec.js
Normal file
406
test/e2e/beta/from-import-beta-ui.spec.js
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const assert = require('assert')
|
||||||
|
const webdriver = require('selenium-webdriver')
|
||||||
|
const { By, Key } = webdriver
|
||||||
|
const {
|
||||||
|
delay,
|
||||||
|
buildChromeWebDriver,
|
||||||
|
buildFirefoxWebdriver,
|
||||||
|
installWebExt,
|
||||||
|
getExtensionIdChrome,
|
||||||
|
getExtensionIdFirefox,
|
||||||
|
} = require('../func')
|
||||||
|
const {
|
||||||
|
checkBrowserForConsoleErrors,
|
||||||
|
loadExtension,
|
||||||
|
verboseReportOnFailure,
|
||||||
|
} = require('./helpers')
|
||||||
|
|
||||||
|
describe('Using MetaMask with an existing account', function () {
|
||||||
|
let extensionId
|
||||||
|
let driver
|
||||||
|
let tokenAddress
|
||||||
|
|
||||||
|
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'
|
||||||
|
const testAddress = '0xE18035BF8712672935FDB4e5e431b1a0183d2DFC'
|
||||||
|
const regularDelayMs = 1000
|
||||||
|
const largeDelayMs = regularDelayMs * 2
|
||||||
|
const waitingNewPageDelayMs = regularDelayMs * 10
|
||||||
|
|
||||||
|
this.timeout(0)
|
||||||
|
this.bail(true)
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
switch (process.env.SELENIUM_BROWSER) {
|
||||||
|
case 'chrome': {
|
||||||
|
const extensionPath = path.resolve('dist/chrome')
|
||||||
|
driver = buildChromeWebDriver(extensionPath)
|
||||||
|
extensionId = await getExtensionIdChrome(driver)
|
||||||
|
await driver.get(`chrome-extension://${extensionId}/popup.html`)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'firefox': {
|
||||||
|
const extensionPath = path.resolve('dist/firefox')
|
||||||
|
driver = buildFirefoxWebdriver()
|
||||||
|
await installWebExt(driver, extensionPath)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
extensionId = await getExtensionIdFirefox(driver)
|
||||||
|
await driver.get(`moz-extension://${extensionId}/popup.html`)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async function () {
|
||||||
|
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
||||||
|
const errors = await checkBrowserForConsoleErrors(driver)
|
||||||
|
if (errors.length) {
|
||||||
|
const errorReports = errors.map(err => err.message)
|
||||||
|
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
|
||||||
|
console.error(new Error(errorMessage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.currentTest.state === 'failed') {
|
||||||
|
await verboseReportOnFailure(driver, this.currentTest)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
await driver.quit()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('New UI setup', async function () {
|
||||||
|
it('switches to first tab', async function () {
|
||||||
|
const [firstTab] = await driver.getAllWindowHandles()
|
||||||
|
await driver.switchTo().window(firstTab)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('use the local network', async function () {
|
||||||
|
const [networkSelector] = await driver.findElements(By.css('#network_component'))
|
||||||
|
await networkSelector.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [localhost] = await driver.findElements(By.xpath(`//li[contains(text(), 'Localhost')]`))
|
||||||
|
await localhost.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('selects the new UI option', async () => {
|
||||||
|
const button = await driver.findElement(By.xpath("//p[contains(text(), 'Try Beta Version')]"))
|
||||||
|
await button.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
// Close all other tabs
|
||||||
|
const [oldUi, infoPage, newUi] = await driver.getAllWindowHandles()
|
||||||
|
await driver.switchTo().window(oldUi)
|
||||||
|
await driver.close()
|
||||||
|
await driver.switchTo().window(infoPage)
|
||||||
|
await driver.close()
|
||||||
|
await driver.switchTo().window(newUi)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [continueBtn] = await driver.findElements(By.css('.welcome-screen__button'))
|
||||||
|
await continueBtn.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('First time flow starting from an existing seed phrase', () => {
|
||||||
|
it('imports a seed phrase', async () => {
|
||||||
|
const [seedPhrase] = await driver.findElements(By.xpath(`//a[contains(text(), 'Import with seed phrase')]`))
|
||||||
|
await seedPhrase.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [seedTextArea] = await driver.findElements(By.css('textarea.import-account__secret-phrase'))
|
||||||
|
await seedTextArea.sendKeys(testSeedPhrase)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [password] = await driver.findElements(By.id('password'))
|
||||||
|
await password.sendKeys('correct horse battery staple')
|
||||||
|
const [confirmPassword] = await driver.findElements(By.id('confirm-password'))
|
||||||
|
confirmPassword.sendKeys('correct horse battery staple')
|
||||||
|
|
||||||
|
const [importButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Import')]`))
|
||||||
|
await importButton.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clicks through the privacy notice', async () => {
|
||||||
|
const [nextScreen] = await driver.findElements(By.css('.tou button'))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
|
||||||
|
assert.equal(canClickThrough, false, 'disabled continue button')
|
||||||
|
const element = await driver.findElement(By.linkText('Attributions'))
|
||||||
|
await driver.executeScript('arguments[0].scrollIntoView(true)', element)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [acceptTos] = await driver.findElements(By.css('.tou button'))
|
||||||
|
await acceptTos.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Show account information', () => {
|
||||||
|
it('shows the correct account address', async () => {
|
||||||
|
await driver.findElement(By.css('.wallet-view__details-button')).click()
|
||||||
|
await driver.findElement(By.css('.qr-wrapper')).isDisplayed()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [address] = await driver.findElements(By.css('input.qr-ellip-address'))
|
||||||
|
assert.equal(await address.getAttribute('value'), testAddress)
|
||||||
|
|
||||||
|
await driver.executeScript("document.querySelector('.account-modal-close').click()")
|
||||||
|
await delay(largeDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows a QR code for the account', async () => {
|
||||||
|
await driver.findElement(By.css('.wallet-view__details-button')).click()
|
||||||
|
await driver.findElement(By.css('.qr-wrapper')).isDisplayed()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
await driver.executeScript("document.querySelector('.account-modal-close').click()")
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Log out and log back in', () => {
|
||||||
|
it('logs out of the account', async () => {
|
||||||
|
await driver.findElement(By.css('.account-menu__icon')).click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [logoutButton] = await driver.findElements(By.css('.account-menu__logout-button'))
|
||||||
|
assert.equal(await logoutButton.getText(), 'Log out')
|
||||||
|
await logoutButton.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('accepts the account password after lock', async () => {
|
||||||
|
await driver.findElement(By.id('password')).sendKeys('correct horse battery staple')
|
||||||
|
await driver.findElement(By.id('password')).sendKeys(Key.ENTER)
|
||||||
|
await delay(largeDelayMs)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Add an account', () => {
|
||||||
|
it('choose Create Account from the account menu', async () => {
|
||||||
|
await driver.findElement(By.css('.account-menu__icon')).click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [createAccount] = await driver.findElements(By.xpath(`//div[contains(text(), 'Create Account')]`))
|
||||||
|
await createAccount.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('set account name', async () => {
|
||||||
|
const [accountName] = await driver.findElements(By.css('.new-account-create-form input'))
|
||||||
|
await accountName.sendKeys('2nd account')
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [createButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create')]`))
|
||||||
|
await createButton.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should show the correct account name', async () => {
|
||||||
|
const [accountName] = await driver.findElements(By.css('.account-name'))
|
||||||
|
assert.equal(await accountName.getText(), '2nd account')
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Switch back to original account', () => {
|
||||||
|
it('chooses the original account from the account menu', async () => {
|
||||||
|
await driver.findElement(By.css('.account-menu__icon')).click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [originalAccountMenuItem] = await driver.findElements(By.css('.account-menu__name'))
|
||||||
|
await originalAccountMenuItem.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Send ETH from inside MetaMask', () => {
|
||||||
|
it('starts to send a transaction', async function () {
|
||||||
|
const [sendButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Send')]`))
|
||||||
|
await sendButton.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [inputAddress] = await driver.findElements(By.css('input[placeholder="Recipient Address"]'))
|
||||||
|
const [inputAmount] = await driver.findElements(By.css('.currency-display__input'))
|
||||||
|
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
||||||
|
await inputAmount.sendKeys('1')
|
||||||
|
|
||||||
|
// Set the gas limit
|
||||||
|
const [configureGas] = await driver.findElements(By.css('.send-v2__gas-fee-display button'))
|
||||||
|
await configureGas.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [save] = await driver.findElements(By.xpath(`//button[contains(text(), 'Save')]`))
|
||||||
|
await save.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
// Continue to next screen
|
||||||
|
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('confirms the transaction', async function () {
|
||||||
|
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||||
|
await confirmButton.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('finds the transaction in the transactions list', async function () {
|
||||||
|
const transactions = await driver.findElements(By.css('.tx-list-item'))
|
||||||
|
assert.equal(transactions.length, 1)
|
||||||
|
|
||||||
|
const txValues = await driver.findElements(By.css('.tx-list-value'))
|
||||||
|
assert.equal(txValues.length, 1)
|
||||||
|
assert.equal(await txValues[0].getText(), '1 ETH')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Send ETH from Faucet', () => {
|
||||||
|
it('starts a send transaction inside Faucet', async () => {
|
||||||
|
await driver.executeScript('window.open("https://faucet.metamask.io")')
|
||||||
|
await delay(waitingNewPageDelayMs)
|
||||||
|
|
||||||
|
const [extension, faucet] = await driver.getAllWindowHandles()
|
||||||
|
await driver.switchTo().window(faucet)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [send1eth] = await driver.findElements(By.xpath(`//button[contains(text(), '10 ether')]`))
|
||||||
|
await send1eth.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
await driver.switchTo().window(extension)
|
||||||
|
await loadExtension(driver, extensionId)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(),'Confirm')]`))
|
||||||
|
await confirmButton.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
await driver.switchTo().window(faucet)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
await driver.close()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
await driver.switchTo().window(extension)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
await loadExtension(driver, extensionId)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Add existing token using search', () => {
|
||||||
|
it('clicks on the Add Token button', async () => {
|
||||||
|
const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`))
|
||||||
|
await addToken.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('picks an existing token', async () => {
|
||||||
|
const [tokenSearch] = await driver.findElements(By.css('input.add-token__input'))
|
||||||
|
await tokenSearch.sendKeys('BAT')
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [token] = await driver.findElements(By.xpath("//div[contains(text(), 'BAT')]"))
|
||||||
|
await token.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`))
|
||||||
|
await addTokens.click()
|
||||||
|
await delay(largeDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the balance for the new token', async () => {
|
||||||
|
const balance = await driver.findElement(By.css('.tx-view .balance-display .token-amount'))
|
||||||
|
const tokenAmount = await balance.getText()
|
||||||
|
assert.equal(tokenAmount, '0BAT')
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Add a custom token from TokenFactory', () => {
|
||||||
|
it('creates a new token', async () => {
|
||||||
|
await driver.executeScript('window.open("https://tokenfactory.surge.sh/#/factory")')
|
||||||
|
await delay(waitingNewPageDelayMs)
|
||||||
|
|
||||||
|
const [extension, tokenFactory] = await driver.getAllWindowHandles()
|
||||||
|
await driver.switchTo().window(tokenFactory)
|
||||||
|
const [
|
||||||
|
totalSupply,
|
||||||
|
tokenName,
|
||||||
|
tokenDecimal,
|
||||||
|
tokenSymbol,
|
||||||
|
] = await driver.findElements(By.css('input'))
|
||||||
|
|
||||||
|
await totalSupply.sendKeys('100')
|
||||||
|
await tokenName.sendKeys('Test')
|
||||||
|
await tokenDecimal.sendKeys('0')
|
||||||
|
await tokenSymbol.sendKeys('TST')
|
||||||
|
|
||||||
|
const [createToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create Token')]`))
|
||||||
|
await createToken.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
await driver.switchTo().window(extension)
|
||||||
|
await loadExtension(driver, extensionId)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(),'Confirm')]`))
|
||||||
|
await confirmButton.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
await driver.switchTo().window(tokenFactory)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
const tokenContactAddress = await driver.findElement(By.css('div > div > div:nth-child(2) > span:nth-child(3)'))
|
||||||
|
tokenAddress = await tokenContactAddress.getText()
|
||||||
|
await driver.close()
|
||||||
|
await driver.switchTo().window(extension)
|
||||||
|
await loadExtension(driver, extensionId)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clicks on the Add Token button', async () => {
|
||||||
|
const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`))
|
||||||
|
await addToken.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('picks the new Test token', async () => {
|
||||||
|
const [addCustomToken] = await driver.findElements(By.xpath("//div[contains(text(), 'Custom Token')]"))
|
||||||
|
await addCustomToken.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [newTokenAddress] = await driver.findElements(By.css('.add-token__add-custom-form input'))
|
||||||
|
await newTokenAddress.sendKeys(tokenAddress)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`))
|
||||||
|
await addTokens.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the balance for the new token', async () => {
|
||||||
|
const [balance] = await driver.findElements(By.css('.tx-view .balance-display .token-amount'))
|
||||||
|
const tokenAmount = await balance.getText()
|
||||||
|
assert.equal(tokenAmount, '100TST')
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
55
test/e2e/beta/helpers.js
Normal file
55
test/e2e/beta/helpers.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const mkdirp = require('mkdirp')
|
||||||
|
const pify = require('pify')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
checkBrowserForConsoleErrors,
|
||||||
|
loadExtension,
|
||||||
|
verboseReportOnFailure,
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadExtension (driver, extensionId) {
|
||||||
|
switch (process.env.SELENIUM_BROWSER) {
|
||||||
|
case 'chrome': {
|
||||||
|
await driver.get(`chrome-extension://${extensionId}/home.html`)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'firefox': {
|
||||||
|
await driver.get(`moz-extension://${extensionId}/home.html`)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkBrowserForConsoleErrors (driver) {
|
||||||
|
const ignoredLogTypes = ['WARNING']
|
||||||
|
const ignoredErrorMessages = [
|
||||||
|
// React throws error warnings on "dataset", but still sets the data-* properties correctly
|
||||||
|
'Warning: Unknown prop `dataset` on ',
|
||||||
|
// Third-party Favicon 404s show up as errors
|
||||||
|
'favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)',
|
||||||
|
// React Development build - known issue blocked by test build sys
|
||||||
|
'Warning: It looks like you\'re using a minified copy of the development build of React.',
|
||||||
|
// Redux Development build - known issue blocked by test build sys
|
||||||
|
'This means that you are running a slower development build of Redux.',
|
||||||
|
]
|
||||||
|
const browserLogs = await driver.manage().logs().get('browser')
|
||||||
|
const errorEntries = browserLogs.filter(entry => !ignoredLogTypes.includes(entry.level.toString()))
|
||||||
|
const errorObjects = errorEntries.map(entry => entry.toJSON())
|
||||||
|
return errorObjects.filter(entry => !ignoredErrorMessages.some(message => entry.message.includes(message)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verboseReportOnFailure (driver, 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)
|
||||||
|
const screenshot = await driver.takeScreenshot()
|
||||||
|
await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' })
|
||||||
|
const htmlSource = await driver.getPageSource()
|
||||||
|
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource)
|
||||||
|
}
|
491
test/e2e/beta/metamask-beta-ui.spec.js
Normal file
491
test/e2e/beta/metamask-beta-ui.spec.js
Normal file
@ -0,0 +1,491 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const assert = require('assert')
|
||||||
|
const webdriver = require('selenium-webdriver')
|
||||||
|
const { By, Key } = webdriver
|
||||||
|
const {
|
||||||
|
delay,
|
||||||
|
buildChromeWebDriver,
|
||||||
|
buildFirefoxWebdriver,
|
||||||
|
installWebExt,
|
||||||
|
getExtensionIdChrome,
|
||||||
|
getExtensionIdFirefox,
|
||||||
|
} = require('../func')
|
||||||
|
const {
|
||||||
|
checkBrowserForConsoleErrors,
|
||||||
|
loadExtension,
|
||||||
|
verboseReportOnFailure,
|
||||||
|
} = require('./helpers')
|
||||||
|
|
||||||
|
describe('MetaMask', function () {
|
||||||
|
let extensionId
|
||||||
|
let driver
|
||||||
|
let tokenAddress
|
||||||
|
|
||||||
|
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'
|
||||||
|
const tinyDelayMs = 500
|
||||||
|
const regularDelayMs = tinyDelayMs * 2
|
||||||
|
const largeDelayMs = regularDelayMs * 2
|
||||||
|
const waitingNewPageDelayMs = regularDelayMs * 10
|
||||||
|
|
||||||
|
this.timeout(0)
|
||||||
|
this.bail(true)
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
switch (process.env.SELENIUM_BROWSER) {
|
||||||
|
case 'chrome': {
|
||||||
|
const extPath = path.resolve('dist/chrome')
|
||||||
|
driver = buildChromeWebDriver(extPath)
|
||||||
|
extensionId = await getExtensionIdChrome(driver)
|
||||||
|
await driver.get(`chrome-extension://${extensionId}/popup.html`)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'firefox': {
|
||||||
|
const extPath = path.resolve('dist/firefox')
|
||||||
|
driver = buildFirefoxWebdriver()
|
||||||
|
await installWebExt(driver, extPath)
|
||||||
|
await delay(700)
|
||||||
|
extensionId = await getExtensionIdFirefox(driver)
|
||||||
|
await driver.get(`moz-extension://${extensionId}/popup.html`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async function () {
|
||||||
|
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
||||||
|
const errors = await checkBrowserForConsoleErrors(driver)
|
||||||
|
if (errors.length) {
|
||||||
|
const errorReports = errors.map(err => err.message)
|
||||||
|
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
|
||||||
|
console.error(new Error(errorMessage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.currentTest.state === 'failed') {
|
||||||
|
await verboseReportOnFailure(this.currentTest)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
await driver.quit()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('New UI setup', async function () {
|
||||||
|
it('switches to first tab', async function () {
|
||||||
|
const [firstTab] = await driver.getAllWindowHandles()
|
||||||
|
await driver.switchTo().window(firstTab)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('use the local network', async function () {
|
||||||
|
const [networkSelector] = await driver.findElements(By.css('#network_component'))
|
||||||
|
await networkSelector.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [localhost] = await driver.findElements(By.xpath(`//li[contains(text(), 'Localhost')]`))
|
||||||
|
await localhost.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('selects the new UI option', async () => {
|
||||||
|
const button = await driver.findElement(By.xpath("//p[contains(text(), 'Try Beta Version')]"))
|
||||||
|
await button.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
// Close all other tabs
|
||||||
|
const [oldUi, infoPage, newUi] = await driver.getAllWindowHandles()
|
||||||
|
await driver.switchTo().window(oldUi)
|
||||||
|
await driver.close()
|
||||||
|
await driver.switchTo().window(infoPage)
|
||||||
|
await driver.close()
|
||||||
|
await driver.switchTo().window(newUi)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [continueBtn] = await driver.findElements(By.css('.welcome-screen__button'))
|
||||||
|
await continueBtn.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Going through the first time flow', () => {
|
||||||
|
it('accepts a secure password', async () => {
|
||||||
|
const [passwordBox] = await driver.findElements(By.css('.create-password #create-password'))
|
||||||
|
const [passwordBoxConfirm] = await driver.findElements(By.css('.create-password #confirm-password'))
|
||||||
|
const [button] = await driver.findElements(By.css('.create-password button'))
|
||||||
|
|
||||||
|
await passwordBox.sendKeys('correct horse battery staple')
|
||||||
|
await passwordBoxConfirm.sendKeys('correct horse battery staple')
|
||||||
|
await button.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clicks through the unique image screen', async () => {
|
||||||
|
const [nextScreen] = await driver.findElements(By.css('.unique-image button'))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clicks through the privacy notice', async () => {
|
||||||
|
const [nextScreen] = await driver.findElements(By.css('.tou button'))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
|
||||||
|
assert.equal(canClickThrough, false, 'disabled continue button')
|
||||||
|
const [bottomOfTos] = await driver.findElements(By.linkText('Attributions'))
|
||||||
|
await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [acceptTos] = await driver.findElements(By.css('.tou button'))
|
||||||
|
await acceptTos.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
let seedPhrase
|
||||||
|
|
||||||
|
it('reveals the seed phrase', async () => {
|
||||||
|
const [revealSeedPhrase] = await driver.findElements(By.css('.backup-phrase__secret-blocker'))
|
||||||
|
await revealSeedPhrase.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
seedPhrase = await driver.findElement(By.css('.backup-phrase__secret-words')).getText()
|
||||||
|
assert.equal(seedPhrase.split(' ').length, 12)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [nextScreen] = await driver.findElements(By.css('.backup-phrase button'))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can retype the seed phrase', async () => {
|
||||||
|
const words = seedPhrase.split(' ')
|
||||||
|
|
||||||
|
const [word0] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[0]}')]`))
|
||||||
|
await word0.click()
|
||||||
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
|
const [word1] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[1]}')]`))
|
||||||
|
await word1.click()
|
||||||
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
|
const [word2] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[2]}')]`))
|
||||||
|
await word2.click()
|
||||||
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
|
const [word3] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[3]}')]`))
|
||||||
|
await word3.click()
|
||||||
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
|
const [word4] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[4]}')]`))
|
||||||
|
await word4.click()
|
||||||
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
|
const [word5] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[5]}')]`))
|
||||||
|
await word5.click()
|
||||||
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
|
const [word6] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[6]}')]`))
|
||||||
|
await word6.click()
|
||||||
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
|
const [word7] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[7]}')]`))
|
||||||
|
await word7.click()
|
||||||
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
|
const [word8] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[8]}')]`))
|
||||||
|
await word8.click()
|
||||||
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
|
const [word9] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[9]}')]`))
|
||||||
|
await word9.click()
|
||||||
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
|
const [word10] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[10]}')]`))
|
||||||
|
await word10.click()
|
||||||
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
|
const [word11] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[11]}')]`))
|
||||||
|
await word11.click()
|
||||||
|
await delay(tinyDelayMs)
|
||||||
|
|
||||||
|
const [confirm] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||||
|
await confirm.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clicks through the deposit modal', async () => {
|
||||||
|
const [closeModal] = await driver.findElements(By.css('.page-container__header-close'))
|
||||||
|
await closeModal.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Show account information', () => {
|
||||||
|
it('shows the QR code for the account', async () => {
|
||||||
|
await driver.findElement(By.css('.wallet-view__details-button')).click()
|
||||||
|
await driver.findElement(By.css('.qr-wrapper')).isDisplayed()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
await driver.executeScript("document.querySelector('.account-modal-close').click()")
|
||||||
|
await delay(regularDelayMs * 4)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Log out an log back in', () => {
|
||||||
|
it('logs out of the account', async () => {
|
||||||
|
await driver.findElement(By.css('.account-menu__icon')).click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [logoutButton] = await driver.findElements(By.css('.account-menu__logout-button'))
|
||||||
|
assert.equal(await logoutButton.getText(), 'Log out')
|
||||||
|
await logoutButton.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('accepts the account password after lock', async () => {
|
||||||
|
await driver.findElement(By.id('password')).sendKeys('correct horse battery staple')
|
||||||
|
await driver.findElement(By.id('password')).sendKeys(Key.ENTER)
|
||||||
|
await delay(regularDelayMs * 4)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Add account', () => {
|
||||||
|
it('choose Create Account from the account menu', async () => {
|
||||||
|
await driver.findElement(By.css('.account-menu__icon')).click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [createAccount] = await driver.findElements(By.xpath(`//div[contains(text(), 'Create Account')]`))
|
||||||
|
await createAccount.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('set account name', async () => {
|
||||||
|
const [accountName] = await driver.findElements(By.css('.new-account-create-form input'))
|
||||||
|
await accountName.sendKeys('2nd account')
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [create] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create')]`))
|
||||||
|
await create.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correct account name', async () => {
|
||||||
|
const [accountName] = await driver.findElements(By.css('.account-name'))
|
||||||
|
assert.equal(await accountName.getText(), '2nd account')
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Import seed phrase', () => {
|
||||||
|
it('logs out of the vault', async () => {
|
||||||
|
await driver.findElement(By.css('.account-menu__icon')).click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [logoutButton] = await driver.findElements(By.css('.account-menu__logout-button'))
|
||||||
|
assert.equal(await logoutButton.getText(), 'Log out')
|
||||||
|
await logoutButton.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('imports seed phrase', async () => {
|
||||||
|
const [restoreSeedLink] = await driver.findElements(By.css('.unlock-page__link--import'))
|
||||||
|
assert.equal(await restoreSeedLink.getText(), 'Import using account seed phrase')
|
||||||
|
await restoreSeedLink.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [seedTextArea] = await driver.findElements(By.css('textarea'))
|
||||||
|
await seedTextArea.sendKeys(testSeedPhrase)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
await driver.findElement(By.id('password-box')).sendKeys('correct horse battery staple')
|
||||||
|
await driver.findElement(By.id('password-box-confirm')).sendKeys('correct horse battery staple')
|
||||||
|
await driver.findElement(By.css('button:nth-child(2)')).click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('balance renders', async () => {
|
||||||
|
const balance = await driver.findElement(By.css('.balance-display .token-amount'))
|
||||||
|
const tokenAmount = await balance.getText()
|
||||||
|
assert.equal(tokenAmount, '100.000 ETH')
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Send ETH from inside MetaMask', () => {
|
||||||
|
it('starts to send a transaction', async function () {
|
||||||
|
const [sendButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Send')]`))
|
||||||
|
await sendButton.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [inputAddress] = await driver.findElements(By.css('input[placeholder="Recipient Address"]'))
|
||||||
|
const [inputAmount] = await driver.findElements(By.css('.currency-display__input'))
|
||||||
|
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
||||||
|
await inputAmount.sendKeys('1')
|
||||||
|
|
||||||
|
// Set the gas limit
|
||||||
|
const [configureGas] = await driver.findElements(By.css('.send-v2__gas-fee-display button'))
|
||||||
|
await configureGas.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [save] = await driver.findElements(By.xpath(`//button[contains(text(), 'Save')]`))
|
||||||
|
await save.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
// Continue to next screen
|
||||||
|
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('confirms the transaction', async function () {
|
||||||
|
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||||
|
await confirmButton.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('finds the transaction in the transactions list', async function () {
|
||||||
|
const transactions = await driver.findElements(By.css('.tx-list-item'))
|
||||||
|
assert.equal(transactions.length, 1)
|
||||||
|
|
||||||
|
const txValues = await driver.findElements(By.css('.tx-list-value'))
|
||||||
|
assert.equal(txValues.length, 1)
|
||||||
|
assert.equal(await txValues[0].getText(), '1 ETH')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Send ETH from Faucet', () => {
|
||||||
|
it('starts a send transaction inside Faucet', async () => {
|
||||||
|
await driver.executeScript('window.open("https://faucet.metamask.io")')
|
||||||
|
await delay(waitingNewPageDelayMs)
|
||||||
|
|
||||||
|
const [extension, faucet] = await driver.getAllWindowHandles()
|
||||||
|
await driver.switchTo().window(faucet)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [send1eth] = await driver.findElements(By.xpath(`//button[contains(text(), '10 ether')]`))
|
||||||
|
await send1eth.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
await driver.switchTo().window(extension)
|
||||||
|
await loadExtension(driver, extensionId)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||||
|
await confirmButton.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
await driver.switchTo().window(faucet)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
await driver.close()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
await driver.switchTo().window(extension)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
await loadExtension(driver, extensionId)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Add existing token using search', () => {
|
||||||
|
it('clicks on the Add Token button', async () => {
|
||||||
|
const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`))
|
||||||
|
await addToken.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can pick a token from the existing options', async () => {
|
||||||
|
const [tokenSearch] = await driver.findElements(By.css('input.add-token__input'))
|
||||||
|
await tokenSearch.sendKeys('BAT')
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [token] = await driver.findElements(By.xpath("//div[contains(text(), 'BAT')]"))
|
||||||
|
await token.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`))
|
||||||
|
await addTokens.click()
|
||||||
|
await delay(largeDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the balance for the chosen token', async () => {
|
||||||
|
const balance = await driver.findElement(By.css('.tx-view .balance-display .token-amount'))
|
||||||
|
const tokenAmount = await balance.getText()
|
||||||
|
assert.equal(tokenAmount, '0BAT')
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Add a custom token from TokenFactory', () => {
|
||||||
|
it('creates a new token', async () => {
|
||||||
|
await driver.executeScript('window.open("https://tokenfactory.surge.sh/#/factory")')
|
||||||
|
await delay(waitingNewPageDelayMs)
|
||||||
|
|
||||||
|
const [extension, tokenFactory] = await driver.getAllWindowHandles()
|
||||||
|
await driver.switchTo().window(tokenFactory)
|
||||||
|
const [
|
||||||
|
totalSupply,
|
||||||
|
tokenName,
|
||||||
|
tokenDecimal,
|
||||||
|
tokenSymbol,
|
||||||
|
] = await driver.findElements(By.css('input'))
|
||||||
|
|
||||||
|
await totalSupply.sendKeys('100')
|
||||||
|
await tokenName.sendKeys('Test')
|
||||||
|
await tokenDecimal.sendKeys('0')
|
||||||
|
await tokenSymbol.sendKeys('TST')
|
||||||
|
|
||||||
|
const [createToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create Token')]`))
|
||||||
|
await createToken.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
await driver.switchTo().window(extension)
|
||||||
|
await loadExtension(driver, extensionId)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||||
|
await confirmButton.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
await driver.switchTo().window(tokenFactory)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
const tokenContactAddress = await driver.findElement(By.css('div > div > div:nth-child(2) > span:nth-child(3)'))
|
||||||
|
tokenAddress = await tokenContactAddress.getText()
|
||||||
|
await driver.close()
|
||||||
|
await driver.switchTo().window(extension)
|
||||||
|
await loadExtension(driver, extensionId)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clicks on the Add Token button', async () => {
|
||||||
|
const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`))
|
||||||
|
await addToken.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('picks the newly created Test token', async () => {
|
||||||
|
const [addCustomToken] = await driver.findElements(By.xpath("//div[contains(text(), 'Custom Token')]"))
|
||||||
|
await addCustomToken.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [newTokenAddress] = await driver.findElements(By.css('.add-token__add-custom-form input'))
|
||||||
|
await newTokenAddress.sendKeys(tokenAddress)
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`))
|
||||||
|
await nextScreen.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
|
const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`))
|
||||||
|
await addTokens.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the balance for the new token', async () => {
|
||||||
|
const [balance] = await driver.findElements(By.css('.tx-view .balance-display .token-amount'))
|
||||||
|
const tokenAmount = await balance.getText()
|
||||||
|
assert.equal(tokenAmount, '100TST')
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
10
test/e2e/beta/run-all.sh
Executable file
10
test/e2e/beta/run-all.sh
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
export PATH="$PATH:./node_modules/.bin"
|
||||||
|
|
||||||
|
shell-parallel -s 'npm run ganache:start' -x 'sleep 5 && mocha test/e2e/beta/metamask-beta-ui.spec'
|
||||||
|
shell-parallel -s 'npm run ganache:start' -x 'sleep 5 && mocha test/e2e/beta/from-import-beta-ui.spec'
|
@ -1,18 +1,63 @@
|
|||||||
require('chromedriver')
|
require('chromedriver')
|
||||||
|
require('geckodriver')
|
||||||
|
const fs = require('fs')
|
||||||
|
const os = require('os')
|
||||||
|
const path = require('path')
|
||||||
const webdriver = require('selenium-webdriver')
|
const webdriver = require('selenium-webdriver')
|
||||||
|
const Command = require('selenium-webdriver/lib/command').Command
|
||||||
|
const By = webdriver.By
|
||||||
|
|
||||||
exports.delay = function delay (time) {
|
module.exports = {
|
||||||
|
delay,
|
||||||
|
buildChromeWebDriver,
|
||||||
|
buildFirefoxWebdriver,
|
||||||
|
installWebExt,
|
||||||
|
getExtensionIdChrome,
|
||||||
|
getExtensionIdFirefox,
|
||||||
|
}
|
||||||
|
|
||||||
|
function delay (time) {
|
||||||
return new Promise(resolve => setTimeout(resolve, time))
|
return new Promise(resolve => setTimeout(resolve, time))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildChromeWebDriver (extPath) {
|
||||||
exports.buildWebDriver = function buildWebDriver (extPath) {
|
const tmpProfile = path.join(os.tmpdir(), fs.mkdtempSync('mm-chrome-profile'));
|
||||||
return new webdriver.Builder()
|
return new webdriver.Builder()
|
||||||
.withCapabilities({
|
.withCapabilities({
|
||||||
chromeOptions: {
|
chromeOptions: {
|
||||||
args: [`load-extension=${extPath}`],
|
args: [
|
||||||
|
`load-extension=${extPath}`,
|
||||||
|
`user-data-dir=${tmpProfile}`,
|
||||||
|
],
|
||||||
|
binary: process.env.SELENIUM_CHROME_BINARY,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.forBrowser('chrome')
|
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildFirefoxWebdriver () {
|
||||||
|
return new webdriver.Builder().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getExtensionIdChrome (driver) {
|
||||||
|
await driver.get('chrome://extensions')
|
||||||
|
const extensionId = await driver.executeScript('return document.querySelector("extensions-manager").shadowRoot.querySelector("extensions-view-manager extensions-item-list").shadowRoot.querySelector("extensions-item:nth-child(2)").getAttribute("id")')
|
||||||
|
return extensionId
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getExtensionIdFirefox (driver) {
|
||||||
|
await driver.get('about:debugging#addons')
|
||||||
|
const extensionId = await driver.findElement(By.css('dd.addon-target-info-content:nth-child(6) > span:nth-child(1)')).getText()
|
||||||
|
return extensionId
|
||||||
|
}
|
||||||
|
|
||||||
|
async function installWebExt (driver, extension) {
|
||||||
|
const cmd = await new Command('moz-install-web-ext')
|
||||||
|
.setParameter('path', path.resolve(extension))
|
||||||
|
.setParameter('temporary', true)
|
||||||
|
|
||||||
|
await driver.getExecutor()
|
||||||
|
.defineCommand(cmd.getName(), 'POST', '/session/:sessionId/moz/addon/install')
|
||||||
|
|
||||||
|
return await driver.schedule(cmd, 'installWebExt(' + extension + ')')
|
||||||
|
}
|
||||||
|
@ -4,26 +4,44 @@ const path = require('path')
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const pify = require('pify')
|
const pify = require('pify')
|
||||||
const webdriver = require('selenium-webdriver')
|
const webdriver = require('selenium-webdriver')
|
||||||
const By = webdriver.By
|
const { By, Key } = webdriver
|
||||||
const { delay, buildWebDriver } = require('./func')
|
const { delay, buildChromeWebDriver, buildFirefoxWebdriver, installWebExt, getExtensionIdChrome, getExtensionIdFirefox } = require('./func')
|
||||||
|
|
||||||
describe('Metamask popup page', function () {
|
describe('Metamask popup page', function () {
|
||||||
let driver
|
let driver, accountAddress, tokenAddress, extensionId
|
||||||
this.seedPhase
|
|
||||||
this.accountAddress
|
|
||||||
this.timeout(0)
|
this.timeout(0)
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
const extPath = path.resolve('dist/chrome')
|
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
||||||
driver = buildWebDriver(extPath)
|
const extPath = path.resolve('dist/chrome')
|
||||||
await driver.get('chrome://extensions-frame')
|
driver = buildChromeWebDriver(extPath)
|
||||||
const elems = await driver.findElements(By.css('.extension-list-item-wrapper'))
|
extensionId = await getExtensionIdChrome(driver)
|
||||||
const extensionId = await elems[1].getAttribute('id')
|
await driver.get(`chrome-extension://${extensionId}/popup.html`)
|
||||||
await driver.get(`chrome-extension://${extensionId}/popup.html`)
|
|
||||||
await delay(500)
|
} else if (process.env.SELENIUM_BROWSER === 'firefox') {
|
||||||
|
const extPath = path.resolve('dist/firefox')
|
||||||
|
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
|
||||||
|
// https://github.com/SeleniumHQ/selenium/issues/2910
|
||||||
|
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
||||||
|
// check for console errors
|
||||||
|
const errors = await checkBrowserForConsoleErrors()
|
||||||
|
if (errors.length) {
|
||||||
|
const errorReports = errors.map(err => err.message)
|
||||||
|
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
|
||||||
|
this.test.error(new Error(errorMessage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// gather extra data if test failed
|
||||||
if (this.currentTest.state === 'failed') {
|
if (this.currentTest.state === 'failed') {
|
||||||
await verboseReportOnFailure(this.currentTest)
|
await verboseReportOnFailure(this.currentTest)
|
||||||
}
|
}
|
||||||
@ -33,105 +51,301 @@ describe('Metamask popup page', function () {
|
|||||||
await driver.quit()
|
await driver.quit()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#onboarding', () => {
|
describe('Setup', function () {
|
||||||
it('should open Metamask.io', async function () {
|
|
||||||
const tabs = await driver.getAllWindowHandles()
|
it('switches to Chrome extensions list', async function () {
|
||||||
await driver.switchTo().window(tabs[0])
|
|
||||||
await delay(300)
|
|
||||||
await setProviderType('localhost')
|
|
||||||
await delay(300)
|
await delay(300)
|
||||||
|
const windowHandles = await driver.getAllWindowHandles()
|
||||||
|
await driver.switchTo().window(windowHandles[0])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should match title', async () => {
|
it('sets provider type to localhost', async function () {
|
||||||
|
await delay(300)
|
||||||
|
await setProviderType('localhost')
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Account Creation', () => {
|
||||||
|
|
||||||
|
it('matches MetaMask title', async () => {
|
||||||
const title = await driver.getTitle()
|
const title = await driver.getTitle()
|
||||||
assert.equal(title, 'MetaMask', 'title matches MetaMask')
|
assert.equal(title, 'MetaMask', 'title matches MetaMask')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show privacy notice', async () => {
|
it('shows privacy notice', async () => {
|
||||||
|
await delay(300)
|
||||||
const privacy = await driver.findElement(By.css('.terms-header')).getText()
|
const privacy = await driver.findElement(By.css('.terms-header')).getText()
|
||||||
assert.equal(privacy, 'PRIVACY NOTICE', 'shows privacy notice')
|
assert.equal(privacy, 'PRIVACY NOTICE', 'shows privacy notice')
|
||||||
driver.findElement(By.css('button')).click()
|
await driver.findElement(By.css('button')).click()
|
||||||
await delay(300)
|
await delay(300)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show terms of use', async () => {
|
it('show terms of use', async () => {
|
||||||
await delay(300)
|
|
||||||
const terms = await driver.findElement(By.css('.terms-header')).getText()
|
const terms = await driver.findElement(By.css('.terms-header')).getText()
|
||||||
assert.equal(terms, 'TERMS OF USE', 'shows terms of use')
|
assert.equal(terms, 'TERMS OF USE', 'shows terms of use')
|
||||||
await delay(300)
|
delay(300)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should be unable to continue without scolling throught the terms of use', async () => {
|
it('checks if the TOU button is disabled', async () => {
|
||||||
const button = await driver.findElement(By.css('button')).isEnabled()
|
const button = await driver.findElement(By.css('button')).isEnabled()
|
||||||
assert.equal(button, false, 'disabled continue button')
|
assert.equal(button, false, 'disabled continue button')
|
||||||
const element = driver.findElement(By.linkText(
|
const element = await driver.findElement(By.linkText('Attributions'))
|
||||||
'Attributions'
|
|
||||||
))
|
|
||||||
await driver.executeScript('arguments[0].scrollIntoView(true)', element)
|
await driver.executeScript('arguments[0].scrollIntoView(true)', element)
|
||||||
await delay(300)
|
await delay(700)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should be able to continue when scrolled to the bottom of terms of use', async () => {
|
it('allows the button to be clicked when scrolled to the bottom of TOU', async () => {
|
||||||
const button = await driver.findElement(By.css('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'))
|
||||||
const buttonEnabled = await button.isEnabled()
|
|
||||||
await delay(500)
|
|
||||||
assert.equal(buttonEnabled, true, 'enabled continue button')
|
|
||||||
await button.click()
|
await button.click()
|
||||||
await delay(300)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should accept password with length of eight', async () => {
|
it('accepts password with length of eight', async () => {
|
||||||
const passwordBox = await driver.findElement(By.id('password-box'))
|
const passwordBox = await driver.findElement(By.id('password-box'))
|
||||||
const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm'))
|
const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm'))
|
||||||
const button = driver.findElement(By.css('button'))
|
const button = await driver.findElements(By.css('button'))
|
||||||
|
|
||||||
passwordBox.sendKeys('123456789')
|
await passwordBox.sendKeys('123456789')
|
||||||
passwordBoxConfirm.sendKeys('123456789')
|
await passwordBoxConfirm.sendKeys('123456789')
|
||||||
|
await button[0].click()
|
||||||
await delay(500)
|
await delay(500)
|
||||||
await button.click()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show value was created and seed phrase', async () => {
|
it('shows value was created and seed phrase', async () => {
|
||||||
await delay(700)
|
await delay(300)
|
||||||
this.seedPhase = await driver.findElement(By.css('.twelve-word-phrase')).getText()
|
const seedPhrase = await driver.findElement(By.css('.twelve-word-phrase')).getText()
|
||||||
const continueAfterSeedPhrase = await driver.findElement(By.css('button'))
|
assert.equal(seedPhrase.split(' ').length, 12)
|
||||||
|
const continueAfterSeedPhrase = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > button:nth-child(4)'))
|
||||||
|
assert.equal(await continueAfterSeedPhrase.getText(), `I'VE COPIED IT SOMEWHERE SAFE`)
|
||||||
await continueAfterSeedPhrase.click()
|
await continueAfterSeedPhrase.click()
|
||||||
await delay(300)
|
await delay(300)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show lock account', async () => {
|
it('adds a second account', async function () {
|
||||||
|
await driver.findElement(By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div')).click()
|
||||||
|
await delay(300)
|
||||||
|
await driver.findElement(By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div > div > span > div > li:nth-child(3) > span')).click()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows account address', async function () {
|
||||||
|
await delay(300)
|
||||||
|
accountAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.flex-row > div')).getText()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs out of the vault', async () => {
|
||||||
await driver.findElement(By.css('.sandwich-expando')).click()
|
await driver.findElement(By.css('.sandwich-expando')).click()
|
||||||
await delay(500)
|
await delay(500)
|
||||||
await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')).click()
|
const logoutButton = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)'))
|
||||||
|
assert.equal(await logoutButton.getText(), 'Log Out')
|
||||||
|
await logoutButton.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should accept account password after lock', async () => {
|
it('accepts account password after lock', async () => {
|
||||||
await delay(500)
|
await delay(500)
|
||||||
await driver.findElement(By.id('password-box')).sendKeys('123456789')
|
await driver.findElement(By.id('password-box')).sendKeys('123456789')
|
||||||
await driver.findElement(By.css('button')).click()
|
await driver.findElement(By.id('password-box')).sendKeys(Key.ENTER)
|
||||||
await delay(500)
|
await delay(500)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show QR code option', async () => {
|
it('shows QR code option', async () => {
|
||||||
await delay(300)
|
await delay(300)
|
||||||
await driver.findElement(By.css('.fa-ellipsis-h')).click()
|
await driver.findElement(By.css('.fa-ellipsis-h')).click()
|
||||||
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click()
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click()
|
||||||
await delay(300)
|
await delay(300)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show the account address', async () => {
|
it('checks QR code address is the same as account details address', async () => {
|
||||||
this.accountAddress = await driver.findElement(By.css('.ellip-address')).getText()
|
const QRaccountAddress = await driver.findElement(By.css('.ellip-address')).getText()
|
||||||
|
assert.equal(accountAddress.toLowerCase(), QRaccountAddress)
|
||||||
await driver.findElement(By.css('.fa-arrow-left')).click()
|
await driver.findElement(By.css('.fa-arrow-left')).click()
|
||||||
await delay(500)
|
await delay(500)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
async function setProviderType(type) {
|
describe('Import Ganache seed phrase', function () {
|
||||||
|
|
||||||
|
it('logs out', async function () {
|
||||||
|
await driver.findElement(By.css('.sandwich-expando')).click()
|
||||||
|
await delay(200)
|
||||||
|
const logOut = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)'))
|
||||||
|
assert.equal(await logOut.getText(), 'Log Out')
|
||||||
|
await logOut.click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('restores from seed phrase', async function () {
|
||||||
|
const restoreSeedLink = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div.flex-row.flex-center.flex-grow > p'))
|
||||||
|
assert.equal(await restoreSeedLink.getText(), 'Restore from seed phrase')
|
||||||
|
await restoreSeedLink.click()
|
||||||
|
await delay(100)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds seed phrase', async function () {
|
||||||
|
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'
|
||||||
|
const seedTextArea = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > textarea'))
|
||||||
|
await seedTextArea.sendKeys(testSeedPhrase)
|
||||||
|
|
||||||
|
await driver.findElement(By.id('password-box')).sendKeys('123456789')
|
||||||
|
await driver.findElement(By.id('password-box-confirm')).sendKeys('123456789')
|
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > button:nth-child(2)')).click()
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('balance renders', async function () {
|
||||||
|
await delay(200)
|
||||||
|
const balance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > div.ether-balance.ether-balance-amount > div > div > div:nth-child(1) > div:nth-child(1)'))
|
||||||
|
assert.equal(await balance.getText(), '100.000')
|
||||||
|
await delay(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends transaction', async function () {
|
||||||
|
const sendButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > button:nth-child(4)'))
|
||||||
|
assert.equal(await sendButton.getText(), 'SEND')
|
||||||
|
await sendButton.click()
|
||||||
|
await delay(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds recipient address and amount', async function () {
|
||||||
|
const sendTranscationScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > h3:nth-child(2)')).getText()
|
||||||
|
assert.equal(sendTranscationScreen, 'SEND TRANSACTION')
|
||||||
|
const inputAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(3) > div > input'))
|
||||||
|
const inputAmmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > input'))
|
||||||
|
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
||||||
|
await inputAmmount.sendKeys('10')
|
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > button')).click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('confirms transaction', async function () {
|
||||||
|
await delay(300)
|
||||||
|
await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input')).click()
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('finds the transaction in the transactions list', async function () {
|
||||||
|
const tranasactionAmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > section > div > div > div > div.ether-balance.ether-balance-amount > div > div > div > div:nth-child(1)'))
|
||||||
|
assert.equal(await tranasactionAmount.getText(), '10.0')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Token Factory', function () {
|
||||||
|
|
||||||
|
it('navigates to token factory', async function () {
|
||||||
|
await driver.get('http://tokenfactory.surge.sh/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('navigates to create token contract link', async function () {
|
||||||
|
const createToken = await driver.findElement(By.css('#bs-example-navbar-collapse-1 > ul > li:nth-child(3) > a'))
|
||||||
|
await createToken.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds input for token', async function () {
|
||||||
|
const totalSupply = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(5) > input'))
|
||||||
|
const tokenName = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(6) > input'))
|
||||||
|
const tokenDecimal = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(7) > input'))
|
||||||
|
const tokenSymbol = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(8) > input'))
|
||||||
|
const createToken = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > button'))
|
||||||
|
|
||||||
|
await totalSupply.sendKeys('100')
|
||||||
|
await tokenName.sendKeys('Test')
|
||||||
|
await tokenDecimal.sendKeys('0')
|
||||||
|
await tokenSymbol.sendKeys('TST')
|
||||||
|
await createToken.click()
|
||||||
|
await delay(1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
// There is an issue with blank confirmation window in Firefox, but the button is still there and the driver is able to clicked (?.?)
|
||||||
|
it('confirms transaction in MetaMask popup', async function () {
|
||||||
|
const windowHandles = await driver.getAllWindowHandles()
|
||||||
|
await driver.switchTo().window(windowHandles[windowHandles.length - 1])
|
||||||
|
const metamaskSubmit = await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input'))
|
||||||
|
await metamaskSubmit.click()
|
||||||
|
await delay(1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('switches back to Token Factory to grab the token contract address', async function () {
|
||||||
|
const windowHandles = await driver.getAllWindowHandles()
|
||||||
|
await driver.switchTo().window(windowHandles[0])
|
||||||
|
const tokenContactAddress = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > span:nth-child(3)'))
|
||||||
|
tokenAddress = await tokenContactAddress.getText()
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('navigates back to MetaMask popup in the tab', async function () {
|
||||||
|
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Add Token', function () {
|
||||||
|
|
||||||
|
it('switches to the add token screen', async function () {
|
||||||
|
const tokensTab = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div > div.inactiveForm.pointer'))
|
||||||
|
assert.equal(await tokensTab.getText(), 'TOKENS')
|
||||||
|
await tokensTab.click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('navigates to the add token screen', async function () {
|
||||||
|
const addTokenButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div.full-flex-height > div > button'))
|
||||||
|
assert.equal(await addTokenButton.getText(), 'ADD TOKEN')
|
||||||
|
await addTokenButton.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checks add token screen rendered', async function () {
|
||||||
|
const addTokenScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.section-title.flex-row.flex-center > h2'))
|
||||||
|
assert.equal(await addTokenScreen.getText(), 'ADD TOKEN')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds token parameters', async function () {
|
||||||
|
const tokenContractAddress = await driver.findElement(By.css('#token-address'))
|
||||||
|
await tokenContractAddress.sendKeys(tokenAddress)
|
||||||
|
await delay(300)
|
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > button')).click()
|
||||||
|
await delay(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checks the token balance', async function () {
|
||||||
|
const tokenBalance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > ol > li:nth-child(2) > h3'))
|
||||||
|
assert.equal(await tokenBalance.getText(), '100 TST')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
async function setProviderType (type) {
|
||||||
await driver.executeScript('window.metamask.setProviderType(arguments[0])', type)
|
await driver.executeScript('window.metamask.setProviderType(arguments[0])', type)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function verboseReportOnFailure(test) {
|
async function checkBrowserForConsoleErrors() {
|
||||||
const artifactDir = `./test-artifacts/${test.title}`
|
const ignoredLogTypes = ['WARNING']
|
||||||
|
const ignoredErrorMessages = [
|
||||||
|
// React throws error warnings on "dataset", but still sets the data-* properties correctly
|
||||||
|
'Warning: Unknown prop `dataset` on ',
|
||||||
|
// Third-party Favicon 404s show up as errors
|
||||||
|
'favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)',
|
||||||
|
// React Development build - known issue blocked by test build sys
|
||||||
|
'Warning: It looks like you\'re using a minified copy of the development build of React.',
|
||||||
|
// Redux Development build - known issue blocked by test build sys
|
||||||
|
'This means that you are running a slower development build of Redux.',
|
||||||
|
]
|
||||||
|
const browserLogs = await driver.manage().logs().get('browser')
|
||||||
|
const errorEntries = browserLogs.filter(entry => !ignoredLogTypes.includes(entry.level.toString()))
|
||||||
|
const errorObjects = errorEntries.map(entry => entry.toJSON())
|
||||||
|
// ignore all errors that contain a message in `ignoredErrorMessages`
|
||||||
|
const matchedErrorObjects = errorObjects.filter(entry => !ignoredErrorMessages.some(message => entry.message.includes(message)))
|
||||||
|
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`
|
const filepathBase = `${artifactDir}/test-failure`
|
||||||
await pify(mkdirp)(artifactDir)
|
await pify(mkdirp)(artifactDir)
|
||||||
// capture screenshot
|
// capture screenshot
|
||||||
|
@ -22,6 +22,11 @@ async function runAddTokenFlowTest (assert, done) {
|
|||||||
selectState.val('add token')
|
selectState.val('add token')
|
||||||
reactTriggerChange(selectState[0])
|
reactTriggerChange(selectState[0])
|
||||||
|
|
||||||
|
// Used to set values on TextField input component
|
||||||
|
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
|
||||||
|
window.HTMLInputElement.prototype, 'value'
|
||||||
|
).set
|
||||||
|
|
||||||
// Check that no tokens have been added
|
// Check that no tokens have been added
|
||||||
assert.ok($('.token-list-item').length === 0, 'no tokens added')
|
assert.ok($('.token-list-item').length === 0, 'no tokens added')
|
||||||
|
|
||||||
@ -31,14 +36,14 @@ async function runAddTokenFlowTest (assert, done) {
|
|||||||
addTokenButton[0].click()
|
addTokenButton[0].click()
|
||||||
|
|
||||||
// Verify Add Token screen
|
// Verify Add Token screen
|
||||||
let addTokenWrapper = await queryAsync($, '.add-token__wrapper')
|
let addTokenWrapper = await queryAsync($, '.page-container')
|
||||||
assert.ok(addTokenWrapper[0], 'add token wrapper renders')
|
assert.ok(addTokenWrapper[0], 'add token wrapper renders')
|
||||||
|
|
||||||
let addTokenTitle = await queryAsync($, '.add-token__header__title')
|
let addTokenTitle = await queryAsync($, '.page-container__title')
|
||||||
assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct')
|
assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct')
|
||||||
|
|
||||||
// Cancel Add Token
|
// Cancel Add Token
|
||||||
const cancelAddTokenButton = await queryAsync($, 'button.btn-secondary--lg.add-token__cancel-button')
|
const cancelAddTokenButton = await queryAsync($, 'button.btn-secondary--lg.page-container__footer-button')
|
||||||
assert.ok(cancelAddTokenButton[0], 'cancel add token button present')
|
assert.ok(cancelAddTokenButton[0], 'cancel add token button present')
|
||||||
cancelAddTokenButton.click()
|
cancelAddTokenButton.click()
|
||||||
|
|
||||||
@ -50,20 +55,22 @@ async function runAddTokenFlowTest (assert, done) {
|
|||||||
addTokenButton[0].click()
|
addTokenButton[0].click()
|
||||||
|
|
||||||
// Verify Add Token Screen
|
// Verify Add Token Screen
|
||||||
addTokenWrapper = await queryAsync($, '.add-token__wrapper')
|
addTokenWrapper = await queryAsync($, '.page-container')
|
||||||
addTokenTitle = await queryAsync($, '.add-token__header__title')
|
addTokenTitle = await queryAsync($, '.page-container__title')
|
||||||
assert.ok(addTokenWrapper[0], 'add token wrapper renders')
|
assert.ok(addTokenWrapper[0], 'add token wrapper renders')
|
||||||
assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct')
|
assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct')
|
||||||
|
|
||||||
// Search for token
|
// Search for token
|
||||||
const searchInput = await queryAsync($, 'input.add-token__input')
|
const searchInput = (await findAsync(addTokenWrapper, '#search-tokens'))[0]
|
||||||
searchInput.val('a')
|
searchInput.focus()
|
||||||
reactTriggerChange(searchInput[0])
|
await timeout(1000)
|
||||||
|
nativeInputValueSetter.call(searchInput, 'a')
|
||||||
|
searchInput.dispatchEvent(new Event('input', { bubbles: true}))
|
||||||
|
|
||||||
// Click token to add
|
// Click token to add
|
||||||
const tokenWrapper = await queryAsync($, 'div.add-token__token-wrapper')
|
const tokenWrapper = await queryAsync($, 'div.token-list__token')
|
||||||
assert.ok(tokenWrapper[0], 'token found')
|
assert.ok(tokenWrapper[0], 'token found')
|
||||||
const tokenImageProp = tokenWrapper.find('.add-token__token-icon').css('background-image')
|
const tokenImageProp = tokenWrapper.find('.token-list__token-icon').css('background-image')
|
||||||
const tokenImageUrl = tokenImageProp.slice(5, -2)
|
const tokenImageUrl = tokenImageProp.slice(5, -2)
|
||||||
tokenWrapper[0].click()
|
tokenWrapper[0].click()
|
||||||
|
|
||||||
@ -73,11 +80,8 @@ async function runAddTokenFlowTest (assert, done) {
|
|||||||
nextButton[0].click()
|
nextButton[0].click()
|
||||||
|
|
||||||
// Confirm Add token
|
// Confirm Add token
|
||||||
assert.equal(
|
const confirmAddToken = await queryAsync($, '.confirm-add-token')
|
||||||
$('.add-token__description')[0].textContent,
|
assert.ok(confirmAddToken[0], 'confirm add token rendered')
|
||||||
'Token balance(s)',
|
|
||||||
'confirm add token rendered'
|
|
||||||
)
|
|
||||||
assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found')
|
assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found')
|
||||||
$('button.btn-primary--lg')[0].click()
|
$('button.btn-primary--lg')[0].click()
|
||||||
|
|
||||||
@ -91,39 +95,46 @@ async function runAddTokenFlowTest (assert, done) {
|
|||||||
assert.ok(addTokenButton[0], 'add token button present')
|
assert.ok(addTokenButton[0], 'add token button present')
|
||||||
addTokenButton[0].click()
|
addTokenButton[0].click()
|
||||||
|
|
||||||
const addTokenTabs = await queryAsync($, '.add-token__header__tabs__tab')
|
addTokenWrapper = await queryAsync($, '.page-container')
|
||||||
|
const addTokenTabs = await queryAsync($, '.page-container__tab')
|
||||||
assert.equal(addTokenTabs.length, 2, 'expected number of tabs')
|
assert.equal(addTokenTabs.length, 2, 'expected number of tabs')
|
||||||
assert.equal(addTokenTabs[1].textContent, 'Custom Token', 'Custom Token tab present')
|
assert.equal(addTokenTabs[1].textContent, 'Custom Token', 'Custom Token tab present')
|
||||||
assert.ok(addTokenTabs[1], 'add custom token tab present')
|
assert.ok(addTokenTabs[1], 'add custom token tab present')
|
||||||
addTokenTabs[1].click()
|
addTokenTabs[1].click()
|
||||||
|
await timeout(1000)
|
||||||
|
|
||||||
// Input token contract address
|
// Input token contract address
|
||||||
const customInput = await queryAsync($, 'input.add-token__add-custom-input')
|
const customInput = (await findAsync(addTokenWrapper, '#custom-address'))[0]
|
||||||
customInput.val('0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c')
|
customInput.focus()
|
||||||
reactTriggerChange(customInput[0])
|
await timeout(1000)
|
||||||
|
nativeInputValueSetter.call(customInput, '0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c')
|
||||||
|
customInput.dispatchEvent(new Event('input', { bubbles: true}))
|
||||||
|
|
||||||
|
|
||||||
// Click Next button
|
// Click Next button
|
||||||
nextButton = await queryAsync($, 'button.btn-primary--lg')
|
// nextButton = await queryAsync($, 'button.btn-primary--lg')
|
||||||
assert.equal(nextButton[0].textContent, 'Next', 'next button rendered')
|
// assert.equal(nextButton[0].textContent, 'Next', 'next button rendered')
|
||||||
nextButton[0].click()
|
// nextButton[0].click()
|
||||||
|
|
||||||
// Verify symbol length error since contract address won't return symbol
|
// // Verify symbol length error since contract address won't return symbol
|
||||||
const errorMessage = await queryAsync($, '.add-token__add-custom-error-message')
|
const errorMessage = await queryAsync($, '#custom-symbol-helper-text')
|
||||||
assert.ok(errorMessage[0], 'error rendered')
|
assert.ok(errorMessage[0], 'error rendered')
|
||||||
|
|
||||||
$('button.btn-secondary--lg')[0].click()
|
$('button.btn-secondary--lg')[0].click()
|
||||||
|
|
||||||
// // Confirm Add token
|
// await timeout(100000)
|
||||||
|
|
||||||
|
// Confirm Add token
|
||||||
// assert.equal(
|
// assert.equal(
|
||||||
// $('.add-token__description')[0].textContent,
|
// $('.page-container__subtitle')[0].textContent,
|
||||||
// 'Would you like to add these tokens?',
|
// 'Would you like to add these tokens?',
|
||||||
// 'confirm add token rendered'
|
// 'confirm add token rendered'
|
||||||
// )
|
// )
|
||||||
// assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found')
|
// assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found')
|
||||||
// $('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($, '.hero-balance')
|
||||||
// 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')
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
const PASSWORD = 'password123'
|
const PASSWORD = 'password123'
|
||||||
const reactTriggerChange = require('react-trigger-change')
|
|
||||||
const {
|
const {
|
||||||
timeout,
|
timeout,
|
||||||
findAsync,
|
findAsync,
|
||||||
@ -11,6 +10,11 @@ async function runFirstTimeUsageTest (assert, done) {
|
|||||||
|
|
||||||
const app = await queryAsync($, '#app-content')
|
const app = await queryAsync($, '#app-content')
|
||||||
|
|
||||||
|
// Used to set values on TextField input component
|
||||||
|
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
|
||||||
|
window.HTMLInputElement.prototype, 'value'
|
||||||
|
).set
|
||||||
|
|
||||||
await skipNotices(app)
|
await skipNotices(app)
|
||||||
|
|
||||||
const welcomeButton = (await findAsync(app, '.welcome-screen__button'))[0]
|
const welcomeButton = (await findAsync(app, '.welcome-screen__button'))[0]
|
||||||
@ -21,12 +25,14 @@ async function runFirstTimeUsageTest (assert, done) {
|
|||||||
assert.equal(title, 'Create Password', 'create password screen')
|
assert.equal(title, 'Create Password', 'create password screen')
|
||||||
|
|
||||||
// enter password
|
// enter password
|
||||||
const pwBox = (await findAsync(app, '.first-time-flow__input'))[0]
|
const pwBox = (await findAsync(app, '#create-password'))[0]
|
||||||
const confBox = (await findAsync(app, '.first-time-flow__input'))[1]
|
const confBox = (await findAsync(app, '#confirm-password'))[0]
|
||||||
pwBox.value = PASSWORD
|
|
||||||
confBox.value = PASSWORD
|
nativeInputValueSetter.call(pwBox, PASSWORD)
|
||||||
reactTriggerChange(pwBox)
|
pwBox.dispatchEvent(new Event('input', { bubbles: true}))
|
||||||
reactTriggerChange(confBox)
|
|
||||||
|
nativeInputValueSetter.call(confBox, PASSWORD)
|
||||||
|
confBox.dispatchEvent(new Event('input', { bubbles: true}))
|
||||||
|
|
||||||
// Create Password
|
// Create Password
|
||||||
const createButton = (await findAsync(app, 'button.first-time-flow__button'))[0]
|
const createButton = (await findAsync(app, 'button.first-time-flow__button'))[0]
|
||||||
@ -71,10 +77,16 @@ async function runFirstTimeUsageTest (assert, done) {
|
|||||||
assert.ok(lock, 'Lock menu item found')
|
assert.ok(lock, 'Lock menu item found')
|
||||||
lock.click()
|
lock.click()
|
||||||
|
|
||||||
const pwBox2 = (await findAsync(app, '#password-box'))[0]
|
await timeout(1000)
|
||||||
pwBox2.value = PASSWORD
|
|
||||||
|
|
||||||
const createButton2 = (await findAsync(app, 'button.primary'))[0]
|
const pwBox2 = (await findAsync(app, '#password'))[0]
|
||||||
|
pwBox2.focus()
|
||||||
|
await timeout(1000)
|
||||||
|
|
||||||
|
nativeInputValueSetter.call(pwBox2, PASSWORD)
|
||||||
|
pwBox2.dispatchEvent(new Event('input', { bubbles: true}))
|
||||||
|
|
||||||
|
const createButton2 = (await findAsync(app, 'button[type="submit"]'))[0]
|
||||||
createButton2.click()
|
createButton2.click()
|
||||||
|
|
||||||
const detail2 = (await findAsync(app, '.wallet-view'))[0]
|
const detail2 = (await findAsync(app, '.wallet-view'))[0]
|
||||||
|
@ -21,7 +21,7 @@ async function runTxListItemsTest(assert, done) {
|
|||||||
selectState.val('tx list items')
|
selectState.val('tx list items')
|
||||||
reactTriggerChange(selectState[0])
|
reactTriggerChange(selectState[0])
|
||||||
|
|
||||||
const metamaskLogo = await queryAsync($, '.left-menu-wrapper')
|
const metamaskLogo = await queryAsync($, '.app-header__logo-container')
|
||||||
assert.ok(metamaskLogo[0], 'metamask logo present')
|
assert.ok(metamaskLogo[0], 'metamask logo present')
|
||||||
metamaskLogo[0].click()
|
metamaskLogo[0].click()
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ async function runTxListItemsTest(assert, done) {
|
|||||||
const failedTx = txListItems[4]
|
const failedTx = txListItems[4]
|
||||||
const failedTxRenderedStatus = await findAsync($(failedTx), '.tx-list-status')
|
const failedTxRenderedStatus = await findAsync($(failedTx), '.tx-list-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]
|
||||||
const shapeShiftTxStatus = await findAsync($(shapeShiftTx), '.flex-column div:eq(1)')
|
const shapeShiftTxStatus = await findAsync($(shapeShiftTx), '.flex-column div:eq(1)')
|
||||||
assert.equal(shapeShiftTxStatus[0].textContent, 'No deposits received', 'shapeShiftTx has correct status')
|
assert.equal(shapeShiftTxStatus[0].textContent, 'No deposits received', 'shapeShiftTx has correct status')
|
||||||
|
@ -5,29 +5,43 @@ const mkdirp = require('mkdirp')
|
|||||||
const rimraf = require('rimraf')
|
const rimraf = require('rimraf')
|
||||||
const webdriver = require('selenium-webdriver')
|
const webdriver = require('selenium-webdriver')
|
||||||
const endOfStream = require('end-of-stream')
|
const endOfStream = require('end-of-stream')
|
||||||
|
const clipboardy = require('clipboardy')
|
||||||
|
const Ethjs = require('ethjs')
|
||||||
const GIFEncoder = require('gifencoder')
|
const GIFEncoder = require('gifencoder')
|
||||||
const pngFileStream = require('png-file-stream')
|
const pngFileStream = require('png-file-stream')
|
||||||
const sizeOfPng = require('image-size/lib/types/png')
|
const sizeOfPng = require('image-size/lib/types/png')
|
||||||
const By = webdriver.By
|
const By = webdriver.By
|
||||||
const { delay, buildWebDriver } = require('./func')
|
|
||||||
const localesIndex = require('../../app/_locales/index.json')
|
const localesIndex = require('../../app/_locales/index.json')
|
||||||
|
const { delay, buildChromeWebDriver, buildFirefoxWebdriver, installWebExt, getExtensionIdChrome, getExtensionIdFirefox } = require('../e2e/func')
|
||||||
|
|
||||||
|
const eth = new Ethjs(new Ethjs.HttpProvider('http://localhost:8545'))
|
||||||
|
|
||||||
let driver
|
let driver
|
||||||
|
let screenshotCount = 0
|
||||||
|
|
||||||
captureAllScreens().catch((err) => {
|
captureAllScreens()
|
||||||
|
.then(async () => {
|
||||||
|
// build screenshots into gif
|
||||||
|
console.log('building gif...')
|
||||||
|
await generateGif()
|
||||||
|
|
||||||
|
await driver.quit()
|
||||||
|
process.exit()
|
||||||
|
})
|
||||||
|
.catch(async (err) => {
|
||||||
try {
|
try {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
verboseReportOnFailure()
|
verboseReportOnFailure({ title: 'something broke' })
|
||||||
driver.quit()
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await driver.quit()
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
async function captureAllScreens() {
|
|
||||||
let screenshotCount = 0
|
|
||||||
|
|
||||||
|
async function captureAllScreens() {
|
||||||
// common names
|
// common names
|
||||||
let button
|
let button
|
||||||
let tabs
|
let tabs
|
||||||
@ -35,12 +49,10 @@ async function captureAllScreens() {
|
|||||||
|
|
||||||
await cleanScreenShotDir()
|
await cleanScreenShotDir()
|
||||||
|
|
||||||
// setup selenium and install extension
|
|
||||||
const extPath = path.resolve('dist/chrome')
|
const extPath = path.resolve('dist/chrome')
|
||||||
driver = buildWebDriver(extPath)
|
driver = buildChromeWebDriver(extPath)
|
||||||
await driver.get('chrome://extensions-frame')
|
const extensionId = await getExtensionIdChrome(driver)
|
||||||
const elems = await driver.findElements(By.css('.extension-list-item-wrapper'))
|
|
||||||
const extensionId = await elems[1].getAttribute('id')
|
|
||||||
await driver.get(`chrome-extension://${extensionId}/home.html`)
|
await driver.get(`chrome-extension://${extensionId}/home.html`)
|
||||||
await delay(500)
|
await delay(500)
|
||||||
tabs = await driver.getAllWindowHandles()
|
tabs = await driver.getAllWindowHandles()
|
||||||
@ -75,10 +87,11 @@ async function captureAllScreens() {
|
|||||||
await driver.findElement(By.css('button')).click()
|
await driver.findElement(By.css('button')).click()
|
||||||
await captureLanguageScreenShots('create password')
|
await captureLanguageScreenShots('create password')
|
||||||
|
|
||||||
const passwordBox = await driver.findElement(By.css('input[type=password]:nth-of-type(1)'))
|
const password = '123456789'
|
||||||
const passwordBoxConfirm = await driver.findElement(By.css('input[type=password]:nth-of-type(2)'))
|
const passwordBox = await driver.findElement(By.css('input#create-password'))
|
||||||
passwordBox.sendKeys('123456789')
|
const passwordBoxConfirm = await driver.findElement(By.css('input#confirm-password'))
|
||||||
passwordBoxConfirm.sendKeys('123456789')
|
passwordBox.sendKeys(password)
|
||||||
|
passwordBoxConfirm.sendKeys(password)
|
||||||
await delay(500)
|
await delay(500)
|
||||||
await captureLanguageScreenShots('choose-password-filled')
|
await captureLanguageScreenShots('choose-password-filled')
|
||||||
|
|
||||||
@ -112,109 +125,123 @@ async function captureAllScreens() {
|
|||||||
await delay(300)
|
await delay(300)
|
||||||
await captureLanguageScreenShots('secret backup phrase - reveal')
|
await captureLanguageScreenShots('secret backup phrase - reveal')
|
||||||
|
|
||||||
|
const seedPhrase = await driver.findElement(By.css('.backup-phrase__secret-words')).getText()
|
||||||
|
const seedPhraseWords = seedPhrase.split(' ')
|
||||||
await driver.findElement(By.css('button')).click()
|
await driver.findElement(By.css('button')).click()
|
||||||
await delay(300)
|
await delay(300)
|
||||||
await captureLanguageScreenShots('confirm secret backup phrase')
|
await captureLanguageScreenShots('confirm secret backup phrase')
|
||||||
|
|
||||||
// finish up
|
// enter seed phrase
|
||||||
console.log('building gif...')
|
const seedPhraseButtons = await driver.findElements(By.css('.backup-phrase__confirm-seed-options > button'))
|
||||||
await generateGif()
|
const seedPhraseButtonWords = await Promise.all(seedPhraseButtons.map(button => button.getText()))
|
||||||
await driver.quit()
|
for (let targetWord of seedPhraseWords) {
|
||||||
return
|
const wordIndex = seedPhraseButtonWords.indexOf(targetWord)
|
||||||
|
if (wordIndex === -1) throw new Error(`Captured seed phrase word "${targetWord}" not in found seed phrase button options ${seedPhraseButtonWords.join(' ')}`)
|
||||||
|
await driver.findElement(By.css(`.backup-phrase__confirm-seed-options > button:nth-child(${wordIndex+1})`)).click()
|
||||||
|
await delay(100)
|
||||||
|
}
|
||||||
|
await captureLanguageScreenShots('confirm secret backup phrase - words selected correctly')
|
||||||
|
|
||||||
//
|
await driver.findElement(By.css('.backup-phrase__content-wrapper .first-time-flow__button')).click()
|
||||||
// await button.click()
|
await delay(300)
|
||||||
// await delay(700)
|
await captureLanguageScreenShots('metamask post-initialize greeter screen deposit ether')
|
||||||
// this.seedPhase = await driver.findElement(By.css('.twelve-word-phrase')).getText()
|
|
||||||
// await captureScreenShot('seed phrase')
|
await driver.findElement(By.css('.page-container__header-close')).click()
|
||||||
//
|
await delay(300)
|
||||||
// const continueAfterSeedPhrase = await driver.findElement(By.css('button'))
|
await captureLanguageScreenShots('metamask account main screen')
|
||||||
// await continueAfterSeedPhrase.click()
|
|
||||||
|
// account details + export private key
|
||||||
|
await driver.findElement(By.css('.wallet-view__name-container > .wallet-view__details-button')).click()
|
||||||
|
await delay(300)
|
||||||
|
await captureLanguageScreenShots('metamask account detail screen')
|
||||||
|
|
||||||
|
await driver.findElement(By.css('.account-modal__button:nth-of-type(2)')).click()
|
||||||
|
await delay(300)
|
||||||
|
await captureLanguageScreenShots('metamask account detail export private key screen - initial')
|
||||||
|
|
||||||
|
await driver.findElement(By.css('.private-key-password > input')).sendKeys(password)
|
||||||
|
await delay(300)
|
||||||
|
await captureLanguageScreenShots('metamask account detail export private key screen - password entered')
|
||||||
|
|
||||||
|
await driver.findElement(By.css('.btn-primary--lg.export-private-key__button')).click()
|
||||||
|
await delay(300)
|
||||||
|
await captureLanguageScreenShots('metamask account detail export private key screen - reveal key')
|
||||||
|
|
||||||
|
await driver.findElement(By.css('.export-private-key__button')).click()
|
||||||
|
await delay(300)
|
||||||
|
await captureLanguageScreenShots('metamask account detail export private key screen - done')
|
||||||
|
|
||||||
|
// get eth from Ganache
|
||||||
|
// const viewAddressButton = await driver.findElement(By.css('.wallet-view__address'))
|
||||||
|
// await driver.actions({ bridge: true }).move({ origin: viewAddressButton }).perform()
|
||||||
|
// console.log('driver.actions', driver.actions({ bridge: true }))
|
||||||
// await delay(300)
|
// await delay(300)
|
||||||
// await captureScreenShot('main screen')
|
// await captureLanguageScreenShots('metamask home - hover copy address')
|
||||||
//
|
|
||||||
// await driver.findElement(By.css('.sandwich-expando')).click()
|
|
||||||
// await delay(500)
|
|
||||||
// await captureScreenShot('menu')
|
|
||||||
|
|
||||||
// await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')).click()
|
await driver.findElement(By.css('.wallet-view__address')).click()
|
||||||
// await captureScreenShot('main screen')
|
await delay(100)
|
||||||
// it('should accept account password after lock', async () => {
|
await captureLanguageScreenShots('metamask home - hover copy address')
|
||||||
// await delay(500)
|
|
||||||
// await driver.findElement(By.id('password-box')).sendKeys('123456789')
|
|
||||||
// await driver.findElement(By.css('button')).click()
|
|
||||||
// await delay(500)
|
|
||||||
// })
|
|
||||||
//
|
|
||||||
// it('should show QR code option', async () => {
|
|
||||||
// await delay(300)
|
|
||||||
// await driver.findElement(By.css('.fa-ellipsis-h')).click()
|
|
||||||
// await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click()
|
|
||||||
// await delay(300)
|
|
||||||
// })
|
|
||||||
//
|
|
||||||
// it('should show the account address', async () => {
|
|
||||||
// this.accountAddress = await driver.findElement(By.css('.ellip-address')).getText()
|
|
||||||
// await driver.findElement(By.css('.fa-arrow-left')).click()
|
|
||||||
// await delay(500)
|
|
||||||
// })
|
|
||||||
|
|
||||||
async function captureLanguageScreenShots(label) {
|
const primaryAddress = clipboardy.readSync()
|
||||||
const nonEnglishLocales = localesIndex.filter(localeMeta => localeMeta.code !== 'en')
|
await requestEther(primaryAddress)
|
||||||
// take english shot
|
// wait for block polling
|
||||||
await captureScreenShot(`${label} (en)`)
|
await delay(10000)
|
||||||
for (let localeMeta of nonEnglishLocales) {
|
await captureLanguageScreenShots('metamask home - has ether')
|
||||||
// set locale and take shot
|
|
||||||
await setLocale(localeMeta.code)
|
}
|
||||||
await delay(300)
|
|
||||||
await captureScreenShot(`${label} (${localeMeta.code})`)
|
|
||||||
}
|
async function captureLanguageScreenShots(label) {
|
||||||
// return locale to english
|
const nonEnglishLocales = localesIndex.filter(localeMeta => localeMeta.code !== 'en')
|
||||||
await setLocale('en')
|
// take english shot
|
||||||
|
await captureScreenShot(`${label} (en)`)
|
||||||
|
for (let localeMeta of nonEnglishLocales) {
|
||||||
|
// set locale and take shot
|
||||||
|
await setLocale(localeMeta.code)
|
||||||
await delay(300)
|
await delay(300)
|
||||||
|
await captureScreenShot(`${label} (${localeMeta.code})`)
|
||||||
}
|
}
|
||||||
|
// return locale to english
|
||||||
|
await setLocale('en')
|
||||||
|
await delay(300)
|
||||||
|
}
|
||||||
|
|
||||||
async function setLocale(code) {
|
async function setLocale(code) {
|
||||||
await driver.executeScript('window.metamask.updateCurrentLocale(arguments[0])', code)
|
await driver.executeScript('window.metamask.updateCurrentLocale(arguments[0])', code)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setProviderType(type) {
|
async function setProviderType(type) {
|
||||||
await driver.executeScript('window.metamask.setProviderType(arguments[0])', type)
|
await driver.executeScript('window.metamask.setProviderType(arguments[0])', type)
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanup
|
async function cleanScreenShotDir() {
|
||||||
await driver.quit()
|
await pify(rimraf)(`./test-artifacts/screens/`)
|
||||||
|
}
|
||||||
|
|
||||||
async function cleanScreenShotDir() {
|
async function captureScreenShot(label) {
|
||||||
await pify(rimraf)(`./test-artifacts/screens/`)
|
const shotIndex = screenshotCount.toString().padStart(4, '0')
|
||||||
}
|
screenshotCount++
|
||||||
|
const artifactDir = `./test-artifacts/screens/`
|
||||||
|
await pify(mkdirp)(artifactDir)
|
||||||
|
// capture screenshot
|
||||||
|
const screenshot = await driver.takeScreenshot()
|
||||||
|
await pify(fs.writeFile)(`${artifactDir}/${shotIndex} - ${label}.png`, screenshot, { encoding: 'base64' })
|
||||||
|
}
|
||||||
|
|
||||||
async function captureScreenShot(label) {
|
async function generateGif(){
|
||||||
const shotIndex = screenshotCount.toString().padStart(4, '0')
|
// calculate screenshot size
|
||||||
screenshotCount++
|
const screenshot = await driver.takeScreenshot()
|
||||||
const artifactDir = `./test-artifacts/screens/`
|
const pngBuffer = Buffer.from(screenshot, 'base64')
|
||||||
await pify(mkdirp)(artifactDir)
|
const size = sizeOfPng.calculate(pngBuffer)
|
||||||
// capture screenshot
|
|
||||||
const screenshot = await driver.takeScreenshot()
|
|
||||||
await pify(fs.writeFile)(`${artifactDir}/${shotIndex} - ${label}.png`, screenshot, { encoding: 'base64' })
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateGif(){
|
// read only the english pngs into gif
|
||||||
// calculate screenshot size
|
const encoder = new GIFEncoder(size.width, size.height)
|
||||||
const screenshot = await driver.takeScreenshot()
|
const stream = pngFileStream('./test-artifacts/screens/* (en).png')
|
||||||
const pngBuffer = Buffer.from(screenshot, 'base64')
|
.pipe(encoder.createWriteStream({ repeat: 0, delay: 1000, quality: 10 }))
|
||||||
const size = sizeOfPng.calculate(pngBuffer)
|
.pipe(fs.createWriteStream('./test-artifacts/screens/walkthrough (en).gif'))
|
||||||
|
|
||||||
// read only the english pngs into gif
|
|
||||||
const encoder = new GIFEncoder(size.width, size.height)
|
|
||||||
const stream = pngFileStream('./test-artifacts/screens/* (en).png')
|
|
||||||
.pipe(encoder.createWriteStream({ repeat: 0, delay: 1000, quality: 10 }))
|
|
||||||
.pipe(fs.createWriteStream('./test-artifacts/screens/walkthrough (en).gif'))
|
|
||||||
|
|
||||||
// wait for end
|
|
||||||
await pify(endOfStream)(stream)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// wait for end
|
||||||
|
await pify(endOfStream)(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function verboseReportOnFailure(test) {
|
async function verboseReportOnFailure(test) {
|
||||||
@ -228,3 +255,8 @@ async function verboseReportOnFailure(test) {
|
|||||||
const htmlSource = await driver.getPageSource()
|
const htmlSource = await driver.getPageSource()
|
||||||
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource)
|
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function requestEther(address) {
|
||||||
|
const accounts = await eth.accounts()
|
||||||
|
await eth.sendTransaction({ from: accounts[0], to: address, value: 1 * 1e18, data: '0x0' })
|
||||||
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,35 +0,0 @@
|
|||||||
// var jsdom = require('mocha-jsdom')
|
|
||||||
var assert = require('assert')
|
|
||||||
var freeze = require('deep-freeze-strict')
|
|
||||||
var path = require('path')
|
|
||||||
|
|
||||||
var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js'))
|
|
||||||
var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
|
|
||||||
|
|
||||||
describe('SAVE_ACCOUNT_LABEL', function () {
|
|
||||||
it('updates the state.metamask.identities[:i].name property of the state to the action.value.label', function () {
|
|
||||||
var initialState = {
|
|
||||||
metamask: {
|
|
||||||
identities: {
|
|
||||||
foo: {
|
|
||||||
name: 'bar',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
freeze(initialState)
|
|
||||||
|
|
||||||
const action = {
|
|
||||||
type: actions.SAVE_ACCOUNT_LABEL,
|
|
||||||
value: {
|
|
||||||
account: 'foo',
|
|
||||||
label: 'baz',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
freeze(action)
|
|
||||||
|
|
||||||
var resultingState = reducers(initialState, action)
|
|
||||||
assert.equal(resultingState.metamask.identities.foo.name, action.value.label)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
34
test/unit/actions/set_account_label_test.js
Normal file
34
test/unit/actions/set_account_label_test.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
const freeze = require('deep-freeze-strict')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js'))
|
||||||
|
const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
|
||||||
|
|
||||||
|
describe('SET_ACCOUNT_LABEL', function () {
|
||||||
|
it('updates the state.metamask.identities[:i].name property of the state to the action.value.label', function () {
|
||||||
|
const initialState = {
|
||||||
|
metamask: {
|
||||||
|
identities: {
|
||||||
|
foo: {
|
||||||
|
name: 'bar',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
freeze(initialState)
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
type: actions.SET_ACCOUNT_LABEL,
|
||||||
|
value: {
|
||||||
|
account: 'foo',
|
||||||
|
label: 'baz',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
freeze(action)
|
||||||
|
|
||||||
|
const resultingState = reducers(initialState, action)
|
||||||
|
assert.equal(resultingState.metamask.identities.foo.name, action.value.label)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -9,7 +9,7 @@ var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'redu
|
|||||||
|
|
||||||
describe('tx confirmation screen', function () {
|
describe('tx confirmation screen', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.sinon = sinon.sandbox.create()
|
this.sinon = sinon.createSandbox()
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const ComposableObservableStore = require('../../app/scripts/lib/ComposableObservableStore')
|
const ComposableObservableStore = require('../../../app/scripts/lib/ComposableObservableStore')
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
|
|
||||||
describe('ComposableObservableStore', () => {
|
describe('ComposableObservableStore', () => {
|
31
test/unit/app/account-import-strategies.spec.js
Normal file
31
test/unit/app/account-import-strategies.spec.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
const path = require('path')
|
||||||
|
const accountImporter = require('../../../app/scripts/account-import-strategies/index')
|
||||||
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
|
||||||
|
describe('Account Import Strategies', function () {
|
||||||
|
const privkey = '0x4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553'
|
||||||
|
const json = '{"version":3,"id":"dbb54385-0a99-437f-83c0-647de9f244c3","address":"a7f92ce3fba24196cf6f4bd2e1eb3db282ba998c","Crypto":{"ciphertext":"bde13d9ade5c82df80281ca363320ce254a8a3a06535bbf6ffdeaf0726b1312c","cipherparams":{"iv":"fbf93718a57f26051b292f072f2e5b41"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"7ffe00488319dec48e4c49a120ca49c6afbde9272854c64d9541c83fc6acdffe","n":8192,"r":8,"p":1},"mac":"2adfd9c4bc1cdac4c85bddfb31d9e21a684e0e050247a70c5698facf6b7d4681"}}'
|
||||||
|
|
||||||
|
it('imports a private key and strips 0x prefix', async function () {
|
||||||
|
const importPrivKey = await accountImporter.importAccount('Private Key', [ privkey ])
|
||||||
|
assert.equal(importPrivKey, ethUtil.stripHexPrefix(privkey))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fails when password is incorrect for keystore', async function () {
|
||||||
|
const wrongPassword = 'password2'
|
||||||
|
|
||||||
|
try {
|
||||||
|
await accountImporter.importAccount('JSON File', [ json, wrongPassword])
|
||||||
|
} catch (error) {
|
||||||
|
assert.equal(error.message, 'Key derivation failed - possibly wrong passphrase')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('imports json string and password to return a private key', async function () {
|
||||||
|
const fileContentsPassword = 'password1'
|
||||||
|
const importJson = await accountImporter.importAccount('JSON File', [ json, fileContentsPassword])
|
||||||
|
assert.equal(importJson, '0x5733876abe94146069ce8bcbabbde2677f2e35fa33e875e92041ed2ac87e5bc7')
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
48
test/unit/app/buy-eth-url.spec.js
Normal file
48
test/unit/app/buy-eth-url.spec.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
const getBuyEthUrl = require('../../../app/scripts/lib/buy-eth-url')
|
||||||
|
|
||||||
|
describe('', function () {
|
||||||
|
const mainnet = {
|
||||||
|
network: '1',
|
||||||
|
amount: 5,
|
||||||
|
address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
|
||||||
|
}
|
||||||
|
const ropsten = {
|
||||||
|
network: '3',
|
||||||
|
}
|
||||||
|
const rinkeby = {
|
||||||
|
network: '4',
|
||||||
|
}
|
||||||
|
const kovan = {
|
||||||
|
network: '42',
|
||||||
|
}
|
||||||
|
|
||||||
|
it('returns coinbase url with amount and address for network 1', function () {
|
||||||
|
const coinbaseUrl = getBuyEthUrl(mainnet)
|
||||||
|
const coinbase = coinbaseUrl.match(/(https:\/\/buy.coinbase.com)/)
|
||||||
|
const amount = coinbaseUrl.match(/(amount)\D\d/)
|
||||||
|
const address = coinbaseUrl.match(/(address)(.*)(?=&)/)
|
||||||
|
|
||||||
|
assert.equal(coinbase[0], 'https://buy.coinbase.com')
|
||||||
|
assert.equal(amount[0], 'amount=5')
|
||||||
|
assert.equal(address[0], 'address=0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc')
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns metamask ropsten faucet for network 3', function () {
|
||||||
|
const ropstenUrl = getBuyEthUrl(ropsten)
|
||||||
|
assert.equal(ropstenUrl, 'https://faucet.metamask.io/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns rinkeby dapp for network 4', function () {
|
||||||
|
const rinkebyUrl = getBuyEthUrl(rinkeby)
|
||||||
|
assert.equal(rinkebyUrl, 'https://www.rinkeby.io/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns kovan github test faucet for network 42', function () {
|
||||||
|
const kovanUrl = getBuyEthUrl(kovan)
|
||||||
|
assert.equal(kovanUrl, 'https://github.com/kovan-testnet/faucet')
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
@ -1,26 +1,26 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const AddressBookController = require('../../app/scripts/controllers/address-book')
|
const AddressBookController = require('../../../../app/scripts/controllers/address-book')
|
||||||
|
|
||||||
const mockKeyringController = {
|
const stubPreferencesStore = {
|
||||||
memStore: {
|
getState: function () {
|
||||||
getState: function () {
|
return {
|
||||||
return {
|
identities: {
|
||||||
identities: {
|
'0x0aaa': {
|
||||||
'0x0aaa': {
|
address: '0x0aaa',
|
||||||
address: '0x0aaa',
|
name: 'owned',
|
||||||
name: 'owned',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
describe('address-book-controller', function () {
|
describe('address-book-controller', function () {
|
||||||
var addressBookController
|
var addressBookController
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
addressBookController = new AddressBookController({}, mockKeyringController)
|
addressBookController = new AddressBookController({
|
||||||
|
preferencesStore: stubPreferencesStore,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('addres book management', function () {
|
describe('addres book management', function () {
|
@ -1,5 +1,5 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const BlacklistController = require('../../app/scripts/controllers/blacklist')
|
const BlacklistController = require('../../../../app/scripts/controllers/blacklist')
|
||||||
|
|
||||||
describe('blacklist controller', function () {
|
describe('blacklist controller', function () {
|
||||||
let blacklistController
|
let blacklistController
|
@ -3,7 +3,7 @@ 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')
|
||||||
|
|
||||||
describe('currency-controller', function () {
|
describe('currency-controller', function () {
|
||||||
var currencyController
|
var currencyController
|
||||||
@ -45,7 +45,6 @@ describe('currency-controller', function () {
|
|||||||
currencyController.updateConversionRate()
|
currencyController.updateConversionRate()
|
||||||
.then(function () {
|
.then(function () {
|
||||||
var result = currencyController.getConversionRate()
|
var result = currencyController.getConversionRate()
|
||||||
console.log('currencyController.getConversionRate:', result)
|
|
||||||
assert.equal(typeof result, 'number')
|
assert.equal(typeof result, 'number')
|
||||||
done()
|
done()
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
@ -1,6 +1,6 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const sinon = require('sinon')
|
const sinon = require('sinon')
|
||||||
const InfuraController = require('../../app/scripts/controllers/infura')
|
const InfuraController = require('../../../../app/scripts/controllers/infura')
|
||||||
|
|
||||||
describe('infura-controller', function () {
|
describe('infura-controller', function () {
|
||||||
let infuraController, sandbox, networkStatus
|
let infuraController, sandbox, networkStatus
|
||||||
@ -8,7 +8,7 @@ describe('infura-controller', function () {
|
|||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
infuraController = new InfuraController()
|
infuraController = new InfuraController()
|
||||||
sandbox = sinon.sandbox.create()
|
sandbox = sinon.createSandbox()
|
||||||
sinon.stub(infuraController, 'checkInfuraNetworkStatus').resolves(response)
|
sinon.stub(infuraController, 'checkInfuraNetworkStatus').resolves(response)
|
||||||
networkStatus = await infuraController.checkInfuraNetworkStatus()
|
networkStatus = await infuraController.checkInfuraNetworkStatus()
|
||||||
})
|
})
|
550
test/unit/app/controllers/metamask-controller-test.js
Normal file
550
test/unit/app/controllers/metamask-controller-test.js
Normal file
@ -0,0 +1,550 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
const sinon = require('sinon')
|
||||||
|
const clone = require('clone')
|
||||||
|
const nock = require('nock')
|
||||||
|
const createThoughStream = require('through2').obj
|
||||||
|
const MetaMaskController = require('../../../../app/scripts/metamask-controller')
|
||||||
|
const blacklistJSON = require('eth-phishing-detect/src/config')
|
||||||
|
const firstTimeState = require('../../../../app/scripts/first-time-state')
|
||||||
|
|
||||||
|
const currentNetworkId = 42
|
||||||
|
const DEFAULT_LABEL = 'Account 1'
|
||||||
|
const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'
|
||||||
|
const TEST_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'
|
||||||
|
const TEST_SEED_ALT = 'setup olympic issue mobile velvet surge alcohol burger horse view reopen gentle'
|
||||||
|
const TEST_ADDRESS_ALT = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'
|
||||||
|
|
||||||
|
describe('MetaMaskController', function () {
|
||||||
|
let metamaskController
|
||||||
|
const sandbox = sinon.createSandbox()
|
||||||
|
const noop = () => {}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
|
||||||
|
nock('https://api.infura.io')
|
||||||
|
.persist()
|
||||||
|
.get('/v2/blacklist')
|
||||||
|
.reply(200, blacklistJSON)
|
||||||
|
|
||||||
|
nock('https://api.infura.io')
|
||||||
|
.get('/v1/ticker/ethusd')
|
||||||
|
.reply(200, '{"base": "ETH", "quote": "USD", "bid": 288.45, "ask": 288.46, "volume": 112888.17569277, "exchange": "bitfinex", "total_volume": 272175.00106721005, "num_exchanges": 8, "timestamp": 1506444677}')
|
||||||
|
|
||||||
|
nock('https://api.infura.io')
|
||||||
|
.get('/v1/ticker/ethjpy')
|
||||||
|
.reply(200, '{"base": "ETH", "quote": "JPY", "bid": 32300.0, "ask": 32400.0, "volume": 247.4616071, "exchange": "kraken", "total_volume": 247.4616071, "num_exchanges": 1, "timestamp": 1506444676}')
|
||||||
|
|
||||||
|
nock('https://api.infura.io')
|
||||||
|
.persist()
|
||||||
|
.get(/.*/)
|
||||||
|
.reply(200)
|
||||||
|
|
||||||
|
metamaskController = new MetaMaskController({
|
||||||
|
showUnapprovedTx: noop,
|
||||||
|
showUnconfirmedMessage: noop,
|
||||||
|
encryptor: {
|
||||||
|
encrypt: function (password, object) {
|
||||||
|
this.object = object
|
||||||
|
return Promise.resolve()
|
||||||
|
},
|
||||||
|
decrypt: function () {
|
||||||
|
return Promise.resolve(this.object)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
initState: clone(firstTimeState),
|
||||||
|
})
|
||||||
|
sandbox.spy(metamaskController.keyringController, 'createNewVaultAndKeychain')
|
||||||
|
sandbox.spy(metamaskController.keyringController, 'createNewVaultAndRestore')
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
nock.cleanAll()
|
||||||
|
sandbox.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getGasPrice', function () {
|
||||||
|
|
||||||
|
it('gives the 50th percentile lowest accepted gas price from recentBlocksController', async function () {
|
||||||
|
const realRecentBlocksController = metamaskController.recentBlocksController
|
||||||
|
metamaskController.recentBlocksController = {
|
||||||
|
store: {
|
||||||
|
getState: () => {
|
||||||
|
return {
|
||||||
|
recentBlocks: [
|
||||||
|
{ gasPrices: [ '0x3b9aca00', '0x174876e800'] },
|
||||||
|
{ gasPrices: [ '0x3b9aca00', '0x174876e800'] },
|
||||||
|
{ gasPrices: [ '0x174876e800', '0x174876e800' ]},
|
||||||
|
{ gasPrices: [ '0x174876e800', '0x174876e800' ]},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const gasPrice = metamaskController.getGasPrice()
|
||||||
|
assert.equal(gasPrice, '0x3b9aca00', 'accurately estimates 50th percentile accepted gas price')
|
||||||
|
|
||||||
|
metamaskController.recentBlocksController = realRecentBlocksController
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#createNewVaultAndKeychain', function () {
|
||||||
|
it('can only create new vault on keyringController once', async function () {
|
||||||
|
const selectStub = sandbox.stub(metamaskController, 'selectFirstIdentity')
|
||||||
|
|
||||||
|
const password = 'a-fake-password'
|
||||||
|
|
||||||
|
await metamaskController.createNewVaultAndKeychain(password)
|
||||||
|
await metamaskController.createNewVaultAndKeychain(password)
|
||||||
|
|
||||||
|
assert(metamaskController.keyringController.createNewVaultAndKeychain.calledOnce)
|
||||||
|
|
||||||
|
selectStub.reset()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#createNewVaultAndRestore', function () {
|
||||||
|
it('should be able to call newVaultAndRestore despite a mistake.', async function () {
|
||||||
|
const password = 'what-what-what'
|
||||||
|
await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch((e) => null)
|
||||||
|
await metamaskController.createNewVaultAndRestore(password, TEST_SEED)
|
||||||
|
|
||||||
|
assert(metamaskController.keyringController.createNewVaultAndRestore.calledTwice)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should clear previous identities after vault restoration', async () => {
|
||||||
|
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED)
|
||||||
|
assert.deepEqual(metamaskController.getState().identities, {
|
||||||
|
[TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL },
|
||||||
|
})
|
||||||
|
|
||||||
|
await metamaskController.preferencesController.setAccountLabel(TEST_ADDRESS, 'Account Foo')
|
||||||
|
assert.deepEqual(metamaskController.getState().identities, {
|
||||||
|
[TEST_ADDRESS]: { address: TEST_ADDRESS, name: 'Account Foo' },
|
||||||
|
})
|
||||||
|
|
||||||
|
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT)
|
||||||
|
assert.deepEqual(metamaskController.getState().identities, {
|
||||||
|
[TEST_ADDRESS_ALT]: { address: TEST_ADDRESS_ALT, name: DEFAULT_LABEL },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getApi', function () {
|
||||||
|
let getApi, state
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
getApi = metamaskController.getApi()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getState', function (done) {
|
||||||
|
getApi.getState((err, res) => {
|
||||||
|
if (err) {
|
||||||
|
done(err)
|
||||||
|
} else {
|
||||||
|
state = res
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert.deepEqual(state, metamaskController.getState())
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('preferencesController', function () {
|
||||||
|
|
||||||
|
it('defaults useBlockie to false', function () {
|
||||||
|
assert.equal(metamaskController.preferencesController.store.getState().useBlockie, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('setUseBlockie to true', function () {
|
||||||
|
metamaskController.setUseBlockie(true, noop)
|
||||||
|
assert.equal(metamaskController.preferencesController.store.getState().useBlockie, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#selectFirstIdentity', function () {
|
||||||
|
let identities, address
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
address = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'
|
||||||
|
identities = {
|
||||||
|
'0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': {
|
||||||
|
'address': address,
|
||||||
|
'name': 'Account 1',
|
||||||
|
},
|
||||||
|
'0xc42edfcc21ed14dda456aa0756c153f7985d8813': {
|
||||||
|
'address': '0xc42edfcc21ed14dda456aa0756c153f7985d8813',
|
||||||
|
'name': 'Account 2',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
metamaskController.preferencesController.store.updateState({ identities })
|
||||||
|
metamaskController.selectFirstIdentity()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('changes preferences controller select address', function () {
|
||||||
|
const preferenceControllerState = metamaskController.preferencesController.store.getState()
|
||||||
|
assert.equal(preferenceControllerState.selectedAddress, address)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('changes metamask controller selected address', function () {
|
||||||
|
const metamaskState = metamaskController.getState()
|
||||||
|
assert.equal(metamaskState.selectedAddress, address)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#setCustomRpc', function () {
|
||||||
|
const customRPC = 'https://custom.rpc/'
|
||||||
|
let rpcTarget
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
|
||||||
|
nock('https://custom.rpc')
|
||||||
|
.post('/')
|
||||||
|
.reply(200)
|
||||||
|
|
||||||
|
rpcTarget = metamaskController.setCustomRpc(customRPC)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
nock.cleanAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns custom RPC that when called', async function () {
|
||||||
|
assert.equal(await rpcTarget, customRPC)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('changes the network controller rpc', function () {
|
||||||
|
const networkControllerState = metamaskController.networkController.store.getState()
|
||||||
|
assert.equal(networkControllerState.provider.rpcTarget, customRPC)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#setCurrentCurrency', function () {
|
||||||
|
let defaultMetaMaskCurrency
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
defaultMetaMaskCurrency = metamaskController.currencyController.getCurrentCurrency()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('defaults to usd', function () {
|
||||||
|
assert.equal(defaultMetaMaskCurrency, 'usd')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets currency to JPY', function () {
|
||||||
|
metamaskController.setCurrentCurrency('JPY', noop)
|
||||||
|
assert.equal(metamaskController.currencyController.getCurrentCurrency(), 'JPY')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#createShapeshifttx', function () {
|
||||||
|
let depositAddress, depositType, shapeShiftTxList
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
nock('https://shapeshift.io')
|
||||||
|
.get('/txStat/3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc')
|
||||||
|
.reply(200, '{"status": "no_deposits", "address": "3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc"}')
|
||||||
|
|
||||||
|
depositAddress = '3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc'
|
||||||
|
depositType = 'ETH'
|
||||||
|
shapeShiftTxList = metamaskController.shapeshiftController.store.getState().shapeShiftTxList
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates a shapeshift tx', async function () {
|
||||||
|
metamaskController.createShapeShiftTx(depositAddress, depositType)
|
||||||
|
assert.equal(shapeShiftTxList[0].depositAddress, depositAddress)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#addNewAccount', function () {
|
||||||
|
let addNewAccount
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
addNewAccount = metamaskController.addNewAccount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('errors when an primary keyring is does not exist', async function () {
|
||||||
|
try {
|
||||||
|
await addNewAccount
|
||||||
|
assert.equal(1 === 0)
|
||||||
|
} catch (e) {
|
||||||
|
assert.equal(e.message, 'MetamaskController - No HD Key Tree found')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#verifyseedPhrase', function () {
|
||||||
|
let seedPhrase, getConfigSeed
|
||||||
|
|
||||||
|
it('errors when no keying is provided', async function () {
|
||||||
|
try {
|
||||||
|
await metamaskController.verifySeedPhrase()
|
||||||
|
} catch (error) {
|
||||||
|
assert.equal(error.message, 'MetamaskController - No HD Key Tree found')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
await metamaskController.createNewVaultAndKeychain('password')
|
||||||
|
seedPhrase = await metamaskController.verifySeedPhrase()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('#placeSeedWords should match the initially created vault seed', function () {
|
||||||
|
|
||||||
|
metamaskController.placeSeedWords((err, result) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err)
|
||||||
|
} else {
|
||||||
|
getConfigSeed = metamaskController.configManager.getSeedWords()
|
||||||
|
assert.equal(result, seedPhrase)
|
||||||
|
assert.equal(result, getConfigSeed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert.equal(getConfigSeed, undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('#addNewAccount', async function () {
|
||||||
|
await metamaskController.addNewAccount()
|
||||||
|
const getAccounts = await metamaskController.keyringController.getAccounts()
|
||||||
|
assert.equal(getAccounts.length, 2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#resetAccount', function () {
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
const selectedAddressStub = sinon.stub(metamaskController.preferencesController, 'getSelectedAddress')
|
||||||
|
const getNetworkstub = sinon.stub(metamaskController.txController.txStateManager, 'getNetwork')
|
||||||
|
|
||||||
|
selectedAddressStub.returns('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc')
|
||||||
|
getNetworkstub.returns(42)
|
||||||
|
|
||||||
|
metamaskController.txController.txStateManager._saveTxList([
|
||||||
|
{ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} },
|
||||||
|
{ id: 2, status: 'rejected', metamaskNetworkId: 32, txParams: {} },
|
||||||
|
{ id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4'} },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('wipes transactions from only the correct network id and with the selected address', async function () {
|
||||||
|
await metamaskController.resetAccount()
|
||||||
|
assert.equal(metamaskController.txController.txStateManager.getTx(1), undefined)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#clearSeedWordCache', function () {
|
||||||
|
|
||||||
|
it('should have set seed words', function () {
|
||||||
|
metamaskController.configManager.setSeedWords('test words')
|
||||||
|
const getConfigSeed = metamaskController.configManager.getSeedWords()
|
||||||
|
assert.equal(getConfigSeed, 'test words')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should clear config seed phrase', function () {
|
||||||
|
metamaskController.configManager.setSeedWords('test words')
|
||||||
|
metamaskController.clearSeedWordCache((err, result) => {
|
||||||
|
if (err) console.log(err)
|
||||||
|
})
|
||||||
|
const getConfigSeed = metamaskController.configManager.getSeedWords()
|
||||||
|
assert.equal(getConfigSeed, null)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#setCurrentLocale', function () {
|
||||||
|
|
||||||
|
it('checks the default currentLocale', function () {
|
||||||
|
const preferenceCurrentLocale = metamaskController.preferencesController.store.getState().currentLocale
|
||||||
|
assert.equal(preferenceCurrentLocale, undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets current locale in preferences controller', function () {
|
||||||
|
metamaskController.setCurrentLocale('ja', noop)
|
||||||
|
const preferenceCurrentLocale = metamaskController.preferencesController.store.getState().currentLocale
|
||||||
|
assert.equal(preferenceCurrentLocale, 'ja')
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#newUnsignedMessage', function () {
|
||||||
|
|
||||||
|
let msgParams, metamaskMsgs, messages, msgId
|
||||||
|
|
||||||
|
const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'
|
||||||
|
const data = '0x43727970746f6b697474696573'
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
|
||||||
|
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT)
|
||||||
|
|
||||||
|
msgParams = {
|
||||||
|
'from': address,
|
||||||
|
'data': data,
|
||||||
|
}
|
||||||
|
|
||||||
|
metamaskController.newUnsignedMessage(msgParams, noop)
|
||||||
|
metamaskMsgs = metamaskController.messageManager.getUnapprovedMsgs()
|
||||||
|
messages = metamaskController.messageManager.messages
|
||||||
|
msgId = Object.keys(metamaskMsgs)[0]
|
||||||
|
messages[0].msgParams.metamaskId = parseInt(msgId)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('persists address from msg params', function () {
|
||||||
|
assert.equal(metamaskMsgs[msgId].msgParams.from, address)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('persists data from msg params', function () {
|
||||||
|
assert.equal(metamaskMsgs[msgId].msgParams.data, data)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets the status to unapproved', function () {
|
||||||
|
assert.equal(metamaskMsgs[msgId].status, 'unapproved')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets the type to eth_sign', function () {
|
||||||
|
assert.equal(metamaskMsgs[msgId].type, 'eth_sign')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rejects the message', function () {
|
||||||
|
const msgIdInt = parseInt(msgId)
|
||||||
|
metamaskController.cancelMessage(msgIdInt, noop)
|
||||||
|
assert.equal(messages[0].status, 'rejected')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('errors when signing a message', async function () {
|
||||||
|
try {
|
||||||
|
await metamaskController.signMessage(messages[0].msgParams)
|
||||||
|
} catch (error) {
|
||||||
|
assert.equal(error.message, 'message length is invalid')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#newUnsignedPersonalMessage', function () {
|
||||||
|
|
||||||
|
it('errors with no from in msgParams', function () {
|
||||||
|
const msgParams = {
|
||||||
|
'data': data,
|
||||||
|
}
|
||||||
|
metamaskController.newUnsignedPersonalMessage(msgParams, function (error) {
|
||||||
|
assert.equal(error.message, 'MetaMask Message Signature: from field is required.')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
let msgParams, metamaskPersonalMsgs, personalMessages, msgId
|
||||||
|
|
||||||
|
const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'
|
||||||
|
const data = '0x43727970746f6b697474696573'
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
|
||||||
|
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT)
|
||||||
|
|
||||||
|
msgParams = {
|
||||||
|
'from': address,
|
||||||
|
'data': data,
|
||||||
|
}
|
||||||
|
|
||||||
|
metamaskController.newUnsignedPersonalMessage(msgParams, noop)
|
||||||
|
metamaskPersonalMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs()
|
||||||
|
personalMessages = metamaskController.personalMessageManager.messages
|
||||||
|
msgId = Object.keys(metamaskPersonalMsgs)[0]
|
||||||
|
personalMessages[0].msgParams.metamaskId = parseInt(msgId)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('persists address from msg params', function () {
|
||||||
|
assert.equal(metamaskPersonalMsgs[msgId].msgParams.from, address)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('persists data from msg params', function () {
|
||||||
|
assert.equal(metamaskPersonalMsgs[msgId].msgParams.data, data)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets the status to unapproved', function () {
|
||||||
|
assert.equal(metamaskPersonalMsgs[msgId].status, 'unapproved')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets the type to personal_sign', function () {
|
||||||
|
assert.equal(metamaskPersonalMsgs[msgId].type, 'personal_sign')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rejects the message', function () {
|
||||||
|
const msgIdInt = parseInt(msgId)
|
||||||
|
metamaskController.cancelPersonalMessage(msgIdInt, noop)
|
||||||
|
assert.equal(personalMessages[0].status, 'rejected')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('errors when signing a message', async function () {
|
||||||
|
await metamaskController.signPersonalMessage(personalMessages[0].msgParams)
|
||||||
|
assert.equal(metamaskPersonalMsgs[msgId].status, 'signed')
|
||||||
|
assert.equal(metamaskPersonalMsgs[msgId].rawSig, '0x6a1b65e2b8ed53cf398a769fad24738f9fbe29841fe6854e226953542c4b6a173473cb152b6b1ae5f06d601d45dd699a129b0a8ca84e78b423031db5baa734741b')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#setupUntrustedCommunication', function () {
|
||||||
|
let streamTest
|
||||||
|
|
||||||
|
const phishingUrl = 'decentral.market'
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
streamTest.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets up phishing stream for untrusted communication ', async function () {
|
||||||
|
await metamaskController.blacklistController.updatePhishingList()
|
||||||
|
|
||||||
|
streamTest = createThoughStream((chunk, enc, cb) => {
|
||||||
|
assert.equal(chunk.name, 'phishing')
|
||||||
|
assert.equal(chunk.data.hostname, phishingUrl)
|
||||||
|
cb()
|
||||||
|
})
|
||||||
|
// console.log(streamTest)
|
||||||
|
metamaskController.setupUntrustedCommunication(streamTest, phishingUrl)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#setupTrustedCommunication', function () {
|
||||||
|
let streamTest
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
streamTest.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets up controller dnode api for trusted communication', function (done) {
|
||||||
|
streamTest = createThoughStream((chunk, enc, cb) => {
|
||||||
|
assert.equal(chunk.name, 'controller')
|
||||||
|
cb()
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
metamaskController.setupTrustedCommunication(streamTest, 'mycrypto.com')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#markAccountsFound', function () {
|
||||||
|
it('adds lost accounts to config manager data', function () {
|
||||||
|
metamaskController.markAccountsFound(noop)
|
||||||
|
const configManagerData = metamaskController.configManager.getData()
|
||||||
|
assert.deepEqual(configManagerData.lostAccounts, [])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#markPasswordForgotten', function () {
|
||||||
|
it('adds and sets forgottenPassword to config data to true', function () {
|
||||||
|
metamaskController.markPasswordForgotten(noop)
|
||||||
|
const configManagerData = metamaskController.configManager.getData()
|
||||||
|
assert.equal(configManagerData.forgottenPassword, true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#unMarkPasswordForgotten', function () {
|
||||||
|
it('adds and sets forgottenPassword to config data to false', function () {
|
||||||
|
metamaskController.unMarkPasswordForgotten(noop)
|
||||||
|
const configManagerData = metamaskController.configManager.getData()
|
||||||
|
assert.equal(configManagerData.forgottenPassword, false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
@ -1,19 +1,17 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const nock = require('nock')
|
const nock = require('nock')
|
||||||
const NetworkController = require('../../app/scripts/controllers/network')
|
const NetworkController = require('../../../../app/scripts/controllers/network')
|
||||||
const {
|
const {
|
||||||
getNetworkDisplayName,
|
getNetworkDisplayName,
|
||||||
getNetworkEndpoints,
|
} = require('../../../../app/scripts/controllers/network/util')
|
||||||
} = require('../../app/scripts/controllers/network/util')
|
|
||||||
|
|
||||||
const { createTestProviderTools } = require('../stub/provider')
|
const { createTestProviderTools } = require('../../../stub/provider')
|
||||||
const providerResultStub = {}
|
const providerResultStub = {}
|
||||||
const provider = createTestProviderTools({ scaffold: providerResultStub }).provider
|
|
||||||
|
|
||||||
describe('# Network Controller', function () {
|
describe('# Network Controller', function () {
|
||||||
let networkController
|
let networkController
|
||||||
const noop = () => {}
|
const noop = () => {}
|
||||||
const networkControllerProviderInit = {
|
const networkControllerProviderConfig = {
|
||||||
getAccounts: noop,
|
getAccounts: noop,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,11 +22,9 @@ describe('# Network Controller', function () {
|
|||||||
.post('/metamask')
|
.post('/metamask')
|
||||||
.reply(200)
|
.reply(200)
|
||||||
|
|
||||||
networkController = new NetworkController({
|
networkController = new NetworkController()
|
||||||
provider,
|
|
||||||
})
|
|
||||||
|
|
||||||
networkController.initializeProvider(networkControllerProviderInit, provider)
|
networkController.initializeProvider(networkControllerProviderConfig)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
@ -38,7 +34,7 @@ describe('# Network Controller', function () {
|
|||||||
describe('network', function () {
|
describe('network', function () {
|
||||||
describe('#provider', function () {
|
describe('#provider', function () {
|
||||||
it('provider should be updatable without reassignment', function () {
|
it('provider should be updatable without reassignment', function () {
|
||||||
networkController.initializeProvider(networkControllerProviderInit, provider)
|
networkController.initializeProvider(networkControllerProviderConfig)
|
||||||
const proxy = networkController._proxy
|
const proxy = networkController._proxy
|
||||||
proxy.setTarget({ test: true, on: () => {} })
|
proxy.setTarget({ test: true, on: () => {} })
|
||||||
assert.ok(proxy.test)
|
assert.ok(proxy.test)
|
||||||
@ -59,12 +55,6 @@ describe('# Network Controller', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#getRpcAddressForType', function () {
|
|
||||||
it('should return the right rpc address', function () {
|
|
||||||
const rpcTarget = networkController.getRpcAddressForType('mainnet')
|
|
||||||
assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe('#setProviderType', function () {
|
describe('#setProviderType', function () {
|
||||||
it('should update provider.type', function () {
|
it('should update provider.type', function () {
|
||||||
networkController.setProviderType('mainnet')
|
networkController.setProviderType('mainnet')
|
||||||
@ -76,16 +66,11 @@ describe('# Network Controller', function () {
|
|||||||
const loading = networkController.isNetworkLoading()
|
const loading = networkController.isNetworkLoading()
|
||||||
assert.ok(loading, 'network is loading')
|
assert.ok(loading, 'network is loading')
|
||||||
})
|
})
|
||||||
it('should set the right rpcTarget', function () {
|
|
||||||
networkController.setProviderType('mainnet')
|
|
||||||
const rpcTarget = networkController.getProviderConfig().rpcTarget
|
|
||||||
assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('# Network utils', () => {
|
describe('Network utils', () => {
|
||||||
it('getNetworkDisplayName should return the correct network name', () => {
|
it('getNetworkDisplayName should return the correct network name', () => {
|
||||||
const tests = [
|
const tests = [
|
||||||
{
|
{
|
||||||
@ -114,9 +99,4 @@ describe('# Network utils', () => {
|
|||||||
|
|
||||||
tests.forEach(({ input, expected }) => assert.equal(getNetworkDisplayName(input), expected))
|
tests.forEach(({ input, expected }) => assert.equal(getNetworkDisplayName(input), expected))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('getNetworkEndpoints should return the correct endpoints', () => {
|
|
||||||
assert.equal(getNetworkEndpoints('networkBeta').ropsten, 'https://ropsten.infura.io/metamask2')
|
|
||||||
assert.equal(getNetworkEndpoints('network').rinkeby, 'https://rinkeby.infura.io/metamask')
|
|
||||||
})
|
|
||||||
})
|
})
|
@ -1,6 +1,6 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const configManagerGen = require('../lib/mock-config-manager')
|
const configManagerGen = require('../../../lib/mock-config-manager')
|
||||||
const NoticeController = require('../../app/scripts/notice-controller')
|
const NoticeController = require('../../../../app/scripts/notice-controller')
|
||||||
|
|
||||||
describe('notice-controller', function () {
|
describe('notice-controller', function () {
|
||||||
var noticeController
|
var noticeController
|
162
test/unit/app/controllers/preferences-controller-test.js
Normal file
162
test/unit/app/controllers/preferences-controller-test.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
const PreferencesController = require('../../../../app/scripts/controllers/preferences')
|
||||||
|
|
||||||
|
describe('preferences controller', function () {
|
||||||
|
let preferencesController
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
preferencesController = new PreferencesController()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('setAddresses', function () {
|
||||||
|
it('should keep a map of addresses to names and addresses in the store', function () {
|
||||||
|
preferencesController.setAddresses([
|
||||||
|
'0xda22le',
|
||||||
|
'0x7e57e2',
|
||||||
|
])
|
||||||
|
|
||||||
|
const {identities} = preferencesController.store.getState()
|
||||||
|
assert.deepEqual(identities, {
|
||||||
|
'0xda22le': {
|
||||||
|
name: 'Account 1',
|
||||||
|
address: '0xda22le',
|
||||||
|
},
|
||||||
|
'0x7e57e2': {
|
||||||
|
name: 'Account 2',
|
||||||
|
address: '0x7e57e2',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should replace its list of addresses', function () {
|
||||||
|
preferencesController.setAddresses([
|
||||||
|
'0xda22le',
|
||||||
|
'0x7e57e2',
|
||||||
|
])
|
||||||
|
preferencesController.setAddresses([
|
||||||
|
'0xda22le77',
|
||||||
|
'0x7e57e277',
|
||||||
|
])
|
||||||
|
|
||||||
|
const {identities} = preferencesController.store.getState()
|
||||||
|
assert.deepEqual(identities, {
|
||||||
|
'0xda22le77': {
|
||||||
|
name: 'Account 1',
|
||||||
|
address: '0xda22le77',
|
||||||
|
},
|
||||||
|
'0x7e57e277': {
|
||||||
|
name: 'Account 2',
|
||||||
|
address: '0x7e57e277',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('setAccountLabel', function () {
|
||||||
|
it('should update a label for the given account', function () {
|
||||||
|
preferencesController.setAddresses([
|
||||||
|
'0xda22le',
|
||||||
|
'0x7e57e2',
|
||||||
|
])
|
||||||
|
|
||||||
|
assert.deepEqual(preferencesController.store.getState().identities['0xda22le'], {
|
||||||
|
name: 'Account 1',
|
||||||
|
address: '0xda22le',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
preferencesController.setAccountLabel('0xda22le', 'Dazzle')
|
||||||
|
assert.deepEqual(preferencesController.store.getState().identities['0xda22le'], {
|
||||||
|
name: 'Dazzle',
|
||||||
|
address: '0xda22le',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getTokens', function () {
|
||||||
|
it('should return an empty list initially', async function () {
|
||||||
|
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||||
|
|
||||||
|
const tokens = preferencesController.getTokens()
|
||||||
|
assert.equal(tokens.length, 0, 'empty list of tokens')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('addToken', function () {
|
||||||
|
it('should add that token to its state', async function () {
|
||||||
|
const address = '0xabcdef1234567'
|
||||||
|
const symbol = 'ABBR'
|
||||||
|
const decimals = 5
|
||||||
|
|
||||||
|
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||||
|
await preferencesController.addToken(address, symbol, decimals)
|
||||||
|
|
||||||
|
const tokens = preferencesController.getTokens()
|
||||||
|
assert.equal(tokens.length, 1, 'one token added')
|
||||||
|
|
||||||
|
const added = tokens[0]
|
||||||
|
assert.equal(added.address, address, 'set address correctly')
|
||||||
|
assert.equal(added.symbol, symbol, 'set symbol correctly')
|
||||||
|
assert.equal(added.decimals, decimals, 'set decimals correctly')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should allow updating a token value', async function () {
|
||||||
|
const address = '0xabcdef1234567'
|
||||||
|
const symbol = 'ABBR'
|
||||||
|
const decimals = 5
|
||||||
|
|
||||||
|
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||||
|
await preferencesController.addToken(address, symbol, decimals)
|
||||||
|
|
||||||
|
const newDecimals = 6
|
||||||
|
await preferencesController.addToken(address, symbol, newDecimals)
|
||||||
|
|
||||||
|
const tokens = preferencesController.getTokens()
|
||||||
|
assert.equal(tokens.length, 1, 'one token added')
|
||||||
|
|
||||||
|
const added = tokens[0]
|
||||||
|
assert.equal(added.address, address, 'set address correctly')
|
||||||
|
assert.equal(added.symbol, symbol, 'set symbol correctly')
|
||||||
|
assert.equal(added.decimals, newDecimals, 'updated decimals correctly')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should allow adding tokens to two separate addresses', async function () {
|
||||||
|
const address = '0xabcdef1234567'
|
||||||
|
const symbol = 'ABBR'
|
||||||
|
const decimals = 5
|
||||||
|
|
||||||
|
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||||
|
await preferencesController.addToken(address, symbol, decimals)
|
||||||
|
assert.equal(preferencesController.getTokens().length, 1, 'one token added for 1st address')
|
||||||
|
|
||||||
|
await preferencesController.setSelectedAddress('0xda22le')
|
||||||
|
await preferencesController.addToken(address, symbol, decimals)
|
||||||
|
assert.equal(preferencesController.getTokens().length, 1, 'one token added for 2nd address')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('removeToken', function () {
|
||||||
|
it('should remove the only token from its state', async function () {
|
||||||
|
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||||
|
await preferencesController.addToken('0xa', 'A', 5)
|
||||||
|
await preferencesController.removeToken('0xa')
|
||||||
|
|
||||||
|
const tokens = preferencesController.getTokens()
|
||||||
|
assert.equal(tokens.length, 0, 'one token removed')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should remove a token from its state', async function () {
|
||||||
|
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||||
|
await preferencesController.addToken('0xa', 'A', 4)
|
||||||
|
await preferencesController.addToken('0xb', 'B', 5)
|
||||||
|
await preferencesController.removeToken('0xa')
|
||||||
|
|
||||||
|
const tokens = preferencesController.getTokens()
|
||||||
|
assert.equal(tokens.length, 1, 'one token removed')
|
||||||
|
|
||||||
|
const [token1] = tokens
|
||||||
|
assert.deepEqual(token1, {address: '0xb', symbol: 'B', decimals: 5})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const sinon = require('sinon')
|
const sinon = require('sinon')
|
||||||
const TokenRatesController = require('../../app/scripts/controllers/token-rates')
|
const TokenRatesController = require('../../../../app/scripts/controllers/token-rates')
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
|
|
||||||
describe('TokenRatesController', () => {
|
describe('TokenRatesController', () => {
|
@ -1,6 +1,6 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const NonceTracker = require('../../app/scripts/controllers/transactions/nonce-tracker')
|
const NonceTracker = require('../../../../../app/scripts/controllers/transactions/nonce-tracker')
|
||||||
const MockTxGen = require('../lib/mock-tx-gen')
|
const MockTxGen = require('../../../../lib/mock-tx-gen')
|
||||||
let providerResultStub = {}
|
let providerResultStub = {}
|
||||||
|
|
||||||
describe('Nonce Tracker', function () {
|
describe('Nonce Tracker', function () {
|
@ -3,9 +3,9 @@ const ethUtil = require('ethereumjs-util')
|
|||||||
const EthTx = require('ethereumjs-tx')
|
const EthTx = require('ethereumjs-tx')
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const clone = require('clone')
|
const clone = require('clone')
|
||||||
const { createTestProviderTools } = require('../stub/provider')
|
const { createTestProviderTools } = require('../../../../stub/provider')
|
||||||
const PendingTransactionTracker = require('../../app/scripts/controllers/transactions/pending-tx-tracker')
|
const PendingTransactionTracker = require('../../../../../app/scripts/controllers/transactions/pending-tx-tracker')
|
||||||
const MockTxGen = require('../lib/mock-tx-gen')
|
const MockTxGen = require('../../../../lib/mock-tx-gen')
|
||||||
const sinon = require('sinon')
|
const sinon = require('sinon')
|
||||||
const noop = () => true
|
const noop = () => true
|
||||||
const currentNetworkId = 42
|
const currentNetworkId = 42
|
||||||
@ -294,7 +294,7 @@ describe('PendingTransactionTracker', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
pendingTxTracker.publishTransaction.reset()
|
pendingTxTracker.publishTransaction.restore()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should publish the transaction', function (done) {
|
it('should publish the transaction', function (done) {
|
@ -4,9 +4,9 @@ const EthTx = require('ethereumjs-tx')
|
|||||||
const EthjsQuery = require('ethjs-query')
|
const EthjsQuery = require('ethjs-query')
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const sinon = require('sinon')
|
const sinon = require('sinon')
|
||||||
const TransactionController = require('../../app/scripts/controllers/transactions')
|
const TransactionController = require('../../../../../app/scripts/controllers/transactions')
|
||||||
const TxGasUtils = require('../../app/scripts/controllers/transactions/tx-gas-utils')
|
const TxGasUtils = require('../../../../../app/scripts/controllers/transactions/tx-gas-utils')
|
||||||
const { createTestProviderTools, getTestAccounts } = require('../stub/provider')
|
const { createTestProviderTools, getTestAccounts } = require('../../../../stub/provider')
|
||||||
|
|
||||||
const noop = () => true
|
const noop = () => true
|
||||||
const currentNetworkId = 42
|
const currentNetworkId = 42
|
@ -3,8 +3,8 @@ const Transaction = require('ethereumjs-tx')
|
|||||||
const BN = require('bn.js')
|
const BN = require('bn.js')
|
||||||
|
|
||||||
|
|
||||||
const { hexToBn, bnToHex } = require('../../app/scripts/lib/util')
|
const { hexToBn, bnToHex } = require('../../../../../app/scripts/lib/util')
|
||||||
const TxUtils = require('../../app/scripts/controllers/transactions/tx-gas-utils')
|
const TxUtils = require('../../../../../app/scripts/controllers/transactions/tx-gas-utils')
|
||||||
|
|
||||||
|
|
||||||
describe('txUtils', function () {
|
describe('txUtils', function () {
|
@ -1,5 +1,5 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const txHelper = require('../../ui/lib/tx-helper')
|
const txHelper = require('../../../../../ui/lib/tx-helper')
|
||||||
|
|
||||||
describe('txHelper', function () {
|
describe('txHelper', function () {
|
||||||
it('always shows the oldest tx first', function () {
|
it('always shows the oldest tx first', function () {
|
@ -0,0 +1,129 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
const txStateHistoryHelper = require('../../../../../app/scripts/controllers/transactions/lib/tx-state-history-helper')
|
||||||
|
const testVault = require('../../../../data/v17-long-history.json')
|
||||||
|
|
||||||
|
describe ('Transaction state history helper', function () {
|
||||||
|
|
||||||
|
describe('#snapshotFromTxMeta', function () {
|
||||||
|
it('should clone deep', function () {
|
||||||
|
const input = {
|
||||||
|
foo: {
|
||||||
|
bar: {
|
||||||
|
bam: 'baz'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const output = txStateHistoryHelper.snapshotFromTxMeta(input)
|
||||||
|
assert('foo' in output, 'has a foo key')
|
||||||
|
assert('bar' in output.foo, 'has a bar key')
|
||||||
|
assert('bam' in output.foo.bar, 'has a bar key')
|
||||||
|
assert.equal(output.foo.bar.bam, 'baz', 'has a baz value')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should remove the history key', function () {
|
||||||
|
const input = { foo: 'bar', history: 'remembered' }
|
||||||
|
const output = txStateHistoryHelper.snapshotFromTxMeta(input)
|
||||||
|
assert(typeof output.history, 'undefined', 'should remove history')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#migrateFromSnapshotsToDiffs', function () {
|
||||||
|
it('migrates history to diffs and can recover original values', function () {
|
||||||
|
testVault.data.TransactionController.transactions.forEach((tx, index) => {
|
||||||
|
const newHistory = txStateHistoryHelper.migrateFromSnapshotsToDiffs(tx.history)
|
||||||
|
newHistory.forEach((newEntry, index) => {
|
||||||
|
if (index === 0) {
|
||||||
|
assert.equal(Array.isArray(newEntry), false, 'initial history item IS NOT a json patch obj')
|
||||||
|
} else {
|
||||||
|
assert.equal(Array.isArray(newEntry), true, 'non-initial history entry IS a json patch obj')
|
||||||
|
}
|
||||||
|
const oldEntry = tx.history[index]
|
||||||
|
const historySubset = newHistory.slice(0, index + 1)
|
||||||
|
const reconstructedValue = txStateHistoryHelper.replayHistory(historySubset)
|
||||||
|
assert.deepEqual(oldEntry, reconstructedValue, 'was able to reconstruct old entry from diffs')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#replayHistory', function () {
|
||||||
|
it('replaying history does not mutate the original obj', function () {
|
||||||
|
const initialState = { test: true, message: 'hello', value: 1 }
|
||||||
|
const diff1 = [{
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/message",
|
||||||
|
"value": "haay",
|
||||||
|
}]
|
||||||
|
const diff2 = [{
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/value",
|
||||||
|
"value": 2,
|
||||||
|
}]
|
||||||
|
const history = [initialState, diff1, diff2]
|
||||||
|
|
||||||
|
const beforeStateSnapshot = JSON.stringify(initialState)
|
||||||
|
const latestState = txStateHistoryHelper.replayHistory(history)
|
||||||
|
const afterStateSnapshot = JSON.stringify(initialState)
|
||||||
|
|
||||||
|
assert.notEqual(initialState, latestState, 'initial state is not the same obj as the latest state')
|
||||||
|
assert.equal(beforeStateSnapshot, afterStateSnapshot, 'initial state is not modified during run')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#generateHistoryEntry', function () {
|
||||||
|
|
||||||
|
function generateHistoryEntryTest(note) {
|
||||||
|
|
||||||
|
const prevState = {
|
||||||
|
someValue: 'value 1',
|
||||||
|
foo: {
|
||||||
|
bar: {
|
||||||
|
bam: 'baz'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextState = {
|
||||||
|
newPropRoot: 'new property - root',
|
||||||
|
someValue: 'value 2',
|
||||||
|
foo: {
|
||||||
|
newPropFirstLevel: 'new property - first level',
|
||||||
|
bar: {
|
||||||
|
bam: 'baz'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const before = new Date().getTime()
|
||||||
|
const result = txStateHistoryHelper.generateHistoryEntry(prevState, nextState, note)
|
||||||
|
const after = new Date().getTime()
|
||||||
|
|
||||||
|
assert.ok(Array.isArray(result))
|
||||||
|
assert.equal(result.length, 3)
|
||||||
|
|
||||||
|
const expectedEntry1 = { op: 'add', path: '/foo/newPropFirstLevel', value: 'new property - first level' }
|
||||||
|
assert.equal(result[0].op, expectedEntry1.op)
|
||||||
|
assert.equal(result[0].path, expectedEntry1.path)
|
||||||
|
assert.equal(result[0].value, expectedEntry1.value)
|
||||||
|
assert.equal(result[0].value, expectedEntry1.value)
|
||||||
|
if (note)
|
||||||
|
assert.equal(result[0].note, note)
|
||||||
|
|
||||||
|
assert.ok(result[0].timestamp >= before && result[0].timestamp <= after)
|
||||||
|
|
||||||
|
const expectedEntry2 = { op: 'replace', path: '/someValue', value: 'value 2' }
|
||||||
|
assert.deepEqual(result[1], expectedEntry2)
|
||||||
|
|
||||||
|
const expectedEntry3 = { op: 'add', path: '/newPropRoot', value: 'new property - root' }
|
||||||
|
assert.deepEqual(result[2], expectedEntry3)
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should generate history entries', function () {
|
||||||
|
generateHistoryEntryTest()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add note to first entry', function () {
|
||||||
|
generateHistoryEntryTest('custom note')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -1,8 +1,8 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const clone = require('clone')
|
const clone = require('clone')
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const TxStateManager = require('../../app/scripts/controllers/transactions/tx-state-manager')
|
const TxStateManager = require('../../../../../app/scripts/controllers/transactions/tx-state-manager')
|
||||||
const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper')
|
const txStateHistoryHelper = require('../../../../../app/scripts/controllers/transactions/lib/tx-state-history-helper')
|
||||||
const noop = () => true
|
const noop = () => true
|
||||||
|
|
||||||
describe('TransactionStateManager', function () {
|
describe('TransactionStateManager', function () {
|
||||||
@ -176,14 +176,21 @@ describe('TransactionStateManager', function () {
|
|||||||
assert.deepEqual(updatedTx.history[0], txStateHistoryHelper.snapshotFromTxMeta(updatedTx), 'first history item is initial state')
|
assert.deepEqual(updatedTx.history[0], txStateHistoryHelper.snapshotFromTxMeta(updatedTx), 'first history item is initial state')
|
||||||
// modify value and updateTx
|
// modify value and updateTx
|
||||||
updatedTx.txParams.gasPrice = desiredGasPrice
|
updatedTx.txParams.gasPrice = desiredGasPrice
|
||||||
|
const before = new Date().getTime()
|
||||||
txStateManager.updateTx(updatedTx)
|
txStateManager.updateTx(updatedTx)
|
||||||
|
const after = new Date().getTime()
|
||||||
// check updated value
|
// check updated value
|
||||||
const result = txStateManager.getTx('1')
|
const result = txStateManager.getTx('1')
|
||||||
assert.equal(result.txParams.gasPrice, desiredGasPrice, 'gas price updated')
|
assert.equal(result.txParams.gasPrice, desiredGasPrice, 'gas price updated')
|
||||||
// validate history was updated
|
// validate history was updated
|
||||||
assert.equal(result.history.length, 2, 'two history items (initial + diff)')
|
assert.equal(result.history.length, 2, 'two history items (initial + diff)')
|
||||||
|
assert.equal(result.history[1].length, 1, 'two history state items (initial + diff)')
|
||||||
|
|
||||||
const expectedEntry = { op: 'replace', path: '/txParams/gasPrice', value: desiredGasPrice }
|
const expectedEntry = { op: 'replace', path: '/txParams/gasPrice', value: desiredGasPrice }
|
||||||
assert.deepEqual(result.history[1], [expectedEntry], 'two history items (initial + diff)')
|
assert.deepEqual(result.history[1][0].op, expectedEntry.op, 'two history items (initial + diff) operation')
|
||||||
|
assert.deepEqual(result.history[1][0].path, expectedEntry.path, 'two history items (initial + diff) path')
|
||||||
|
assert.deepEqual(result.history[1][0].value, expectedEntry.value, 'two history items (initial + diff) value')
|
||||||
|
assert.ok(result.history[1][0].timestamp >= before && result.history[1][0].timestamp <= after)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const txUtils = require('../../app/scripts/controllers/transactions/lib/util')
|
const txUtils = require('../../../../../app/scripts/controllers/transactions/lib/util')
|
||||||
|
|
||||||
|
|
||||||
describe('txUtils', function () {
|
describe('txUtils', function () {
|
@ -1,6 +1,6 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
|
|
||||||
const EdgeEncryptor = require('../../app/scripts/edge-encryptor')
|
const EdgeEncryptor = require('../../../app/scripts/edge-encryptor')
|
||||||
|
|
||||||
var password = 'passw0rd1'
|
var password = 'passw0rd1'
|
||||||
var data = 'some random data'
|
var data = 'some random data'
|
@ -1,5 +1,5 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const MessageManager = require('../../app/scripts/lib/message-manager')
|
const MessageManager = require('../../../app/scripts/lib/message-manager')
|
||||||
|
|
||||||
describe('Message Manager', function () {
|
describe('Message Manager', function () {
|
||||||
let messageManager
|
let messageManager
|
@ -1,5 +1,5 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const nodeify = require('../../app/scripts/lib/nodeify')
|
const nodeify = require('../../../app/scripts/lib/nodeify')
|
||||||
|
|
||||||
describe('nodeify', function () {
|
describe('nodeify', function () {
|
||||||
var obj = {
|
var obj = {
|
@ -1,6 +1,6 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const PendingBalanceCalculator = require('../../app/scripts/lib/pending-balance-calculator')
|
const PendingBalanceCalculator = require('../../../app/scripts/lib/pending-balance-calculator')
|
||||||
const MockTxGen = require('../lib/mock-tx-gen')
|
const MockTxGen = require('../../lib/mock-tx-gen')
|
||||||
const BN = require('ethereumjs-util').BN
|
const BN = require('ethereumjs-util').BN
|
||||||
let providerResultStub = {}
|
let providerResultStub = {}
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
|
|
||||||
const PersonalMessageManager = require('../../app/scripts/lib/personal-message-manager')
|
const PersonalMessageManager = require('../../../app/scripts/lib/personal-message-manager')
|
||||||
|
|
||||||
describe('Personal Message Manager', function () {
|
describe('Personal Message Manager', function () {
|
||||||
let messageManager
|
let messageManager
|
@ -1,9 +1,9 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const clone = require('clone')
|
const clone = require('clone')
|
||||||
const KeyringController = require('eth-keyring-controller')
|
const KeyringController = require('eth-keyring-controller')
|
||||||
const firstTimeState = require('../../app/scripts/first-time-state')
|
const firstTimeState = require('../../../app/scripts/first-time-state')
|
||||||
const seedPhraseVerifier = require('../../app/scripts/lib/seed-phrase-verifier')
|
const seedPhraseVerifier = require('../../../app/scripts/lib/seed-phrase-verifier')
|
||||||
const mockEncryptor = require('../lib/mock-encryptor')
|
const mockEncryptor = require('../../lib/mock-encryptor')
|
||||||
|
|
||||||
describe('SeedPhraseVerifier', function () {
|
describe('SeedPhraseVerifier', function () {
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const { sufficientBalance } = require('../../app/scripts/lib/util')
|
const { sufficientBalance } = require('../../../app/scripts/lib/util')
|
||||||
|
|
||||||
|
|
||||||
describe('SufficientBalance', function () {
|
describe('SufficientBalance', function () {
|
@ -1,120 +0,0 @@
|
|||||||
const assert = require('assert')
|
|
||||||
const sinon = require('sinon')
|
|
||||||
const clone = require('clone')
|
|
||||||
const nock = require('nock')
|
|
||||||
const MetaMaskController = require('../../app/scripts/metamask-controller')
|
|
||||||
const blacklistJSON = require('../stub/blacklist')
|
|
||||||
const firstTimeState = require('../../app/scripts/first-time-state')
|
|
||||||
|
|
||||||
const DEFAULT_LABEL = 'Account 1'
|
|
||||||
const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'
|
|
||||||
const TEST_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'
|
|
||||||
const TEST_SEED_ALT = 'setup olympic issue mobile velvet surge alcohol burger horse view reopen gentle'
|
|
||||||
const TEST_ADDRESS_ALT = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'
|
|
||||||
|
|
||||||
describe('MetaMaskController', function () {
|
|
||||||
let metamaskController
|
|
||||||
const sandbox = sinon.sandbox.create()
|
|
||||||
const noop = () => { }
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
|
|
||||||
nock('https://api.infura.io')
|
|
||||||
.persist()
|
|
||||||
.get('/v2/blacklist')
|
|
||||||
.reply(200, blacklistJSON)
|
|
||||||
|
|
||||||
nock('https://api.infura.io')
|
|
||||||
.persist()
|
|
||||||
.get(/.*/)
|
|
||||||
.reply(200)
|
|
||||||
|
|
||||||
metamaskController = new MetaMaskController({
|
|
||||||
showUnapprovedTx: noop,
|
|
||||||
encryptor: {
|
|
||||||
encrypt: function (password, object) {
|
|
||||||
this.object = object
|
|
||||||
return Promise.resolve()
|
|
||||||
},
|
|
||||||
decrypt: function () {
|
|
||||||
return Promise.resolve(this.object)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
initState: clone(firstTimeState),
|
|
||||||
})
|
|
||||||
sandbox.spy(metamaskController.keyringController, 'createNewVaultAndKeychain')
|
|
||||||
sandbox.spy(metamaskController.keyringController, 'createNewVaultAndRestore')
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
nock.cleanAll()
|
|
||||||
sandbox.restore()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#getGasPrice', function () {
|
|
||||||
it('gives the 50th percentile lowest accepted gas price from recentBlocksController', async function () {
|
|
||||||
const realRecentBlocksController = metamaskController.recentBlocksController
|
|
||||||
metamaskController.recentBlocksController = {
|
|
||||||
store: {
|
|
||||||
getState: () => {
|
|
||||||
return {
|
|
||||||
recentBlocks: [
|
|
||||||
{ gasPrices: [ '0x3b9aca00', '0x174876e800'] },
|
|
||||||
{ gasPrices: [ '0x3b9aca00', '0x174876e800'] },
|
|
||||||
{ gasPrices: [ '0x174876e800', '0x174876e800' ]},
|
|
||||||
{ gasPrices: [ '0x174876e800', '0x174876e800' ]},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const gasPrice = metamaskController.getGasPrice()
|
|
||||||
assert.equal(gasPrice, '0x3b9aca00', 'accurately estimates 50th percentile accepted gas price')
|
|
||||||
|
|
||||||
metamaskController.recentBlocksController = realRecentBlocksController
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#createNewVaultAndKeychain', function () {
|
|
||||||
it('can only create new vault on keyringController once', async function () {
|
|
||||||
const selectStub = sandbox.stub(metamaskController, 'selectFirstIdentity')
|
|
||||||
|
|
||||||
const password = 'a-fake-password'
|
|
||||||
|
|
||||||
await metamaskController.createNewVaultAndKeychain(password)
|
|
||||||
await metamaskController.createNewVaultAndKeychain(password)
|
|
||||||
|
|
||||||
assert(metamaskController.keyringController.createNewVaultAndKeychain.calledOnce)
|
|
||||||
|
|
||||||
selectStub.reset()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#createNewVaultAndRestore', function () {
|
|
||||||
it('should be able to call newVaultAndRestore despite a mistake.', async function () {
|
|
||||||
const password = 'what-what-what'
|
|
||||||
await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch((e) => null)
|
|
||||||
await metamaskController.createNewVaultAndRestore(password, TEST_SEED)
|
|
||||||
|
|
||||||
assert(metamaskController.keyringController.createNewVaultAndRestore.calledTwice)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should clear previous identities after vault restoration', async () => {
|
|
||||||
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED)
|
|
||||||
assert.deepEqual(metamaskController.getState().identities, {
|
|
||||||
[TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL },
|
|
||||||
})
|
|
||||||
|
|
||||||
await metamaskController.keyringController.saveAccountLabel(TEST_ADDRESS, 'Account Foo')
|
|
||||||
assert.deepEqual(metamaskController.getState().identities, {
|
|
||||||
[TEST_ADDRESS]: { address: TEST_ADDRESS, name: 'Account Foo' },
|
|
||||||
})
|
|
||||||
|
|
||||||
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT)
|
|
||||||
assert.deepEqual(metamaskController.getState().identities, {
|
|
||||||
[TEST_ADDRESS_ALT]: { address: TEST_ADDRESS_ALT, name: DEFAULT_LABEL },
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
41
test/unit/migrations/026-test.js
Normal file
41
test/unit/migrations/026-test.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
const migration26 = require('../../../app/scripts/migrations/026')
|
||||||
|
const oldStorage = {
|
||||||
|
'meta': {'version': 25},
|
||||||
|
'data': {
|
||||||
|
'PreferencesController': {},
|
||||||
|
'KeyringController': {
|
||||||
|
'walletNicknames': {
|
||||||
|
'0x1e77e2': 'Test Account 1',
|
||||||
|
'0x7e57e2': 'Test Account 2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('migration #26', () => {
|
||||||
|
it('should move the identities from KeyringController', (done) => {
|
||||||
|
migration26.migrate(oldStorage)
|
||||||
|
.then((newStorage) => {
|
||||||
|
const identities = newStorage.data.PreferencesController.identities
|
||||||
|
assert.deepEqual(identities, {
|
||||||
|
'0x1e77e2': {name: 'Test Account 1', address: '0x1e77e2'},
|
||||||
|
'0x7e57e2': {name: 'Test Account 2', address: '0x7e57e2'},
|
||||||
|
})
|
||||||
|
assert.strictEqual(newStorage.data.KeyringController.walletNicknames, undefined)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
.catch(done)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should successfully migrate first time state', (done) => {
|
||||||
|
migration26.migrate({
|
||||||
|
meta: {},
|
||||||
|
data: require('../../../app/scripts/first-time-state'),
|
||||||
|
})
|
||||||
|
.then((migratedData) => {
|
||||||
|
assert.equal(migratedData.meta.version, migration26.version)
|
||||||
|
done()
|
||||||
|
}).catch(done)
|
||||||
|
})
|
||||||
|
})
|
@ -1,22 +1,22 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
const wallet1 = require(path.join('..', 'lib', 'migrations', '001.json'))
|
const wallet1 = require(path.join('..', '..', 'lib', 'migrations', '001.json'))
|
||||||
const vault4 = require(path.join('..', 'lib', 'migrations', '004.json'))
|
const vault4 = require(path.join('..', '..', 'lib', 'migrations', '004.json'))
|
||||||
let vault5, vault6, vault7, vault8, vault9 // vault10, vault11
|
let vault5, vault6, vault7, vault8, vault9 // vault10, vault11
|
||||||
|
|
||||||
const migration2 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '002'))
|
const migration2 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '002'))
|
||||||
const migration3 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '003'))
|
const migration3 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '003'))
|
||||||
const migration4 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '004'))
|
const migration4 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '004'))
|
||||||
const migration5 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '005'))
|
const migration5 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '005'))
|
||||||
const migration6 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '006'))
|
const migration6 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '006'))
|
||||||
const migration7 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '007'))
|
const migration7 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '007'))
|
||||||
const migration8 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '008'))
|
const migration8 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '008'))
|
||||||
const migration9 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '009'))
|
const migration9 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '009'))
|
||||||
const migration10 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '010'))
|
const migration10 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '010'))
|
||||||
const migration11 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '011'))
|
const migration11 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '011'))
|
||||||
const migration12 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '012'))
|
const migration12 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '012'))
|
||||||
const migration13 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '013'))
|
const migration13 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '013'))
|
||||||
|
|
||||||
|
|
||||||
const oldTestRpc = 'https://rawtestrpc.metamask.io/'
|
const oldTestRpc = 'https://rawtestrpc.metamask.io/'
|
@ -1,7 +1,7 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const clone = require('clone')
|
const clone = require('clone')
|
||||||
const Migrator = require('../../app/scripts/lib/migrator/')
|
const Migrator = require('../../../app/scripts/lib/migrator/')
|
||||||
const liveMigrations = require('../../app/scripts/migrations/')
|
const liveMigrations = require('../../../app/scripts/migrations/')
|
||||||
const stubMigrations = [
|
const stubMigrations = [
|
||||||
{
|
{
|
||||||
version: 1,
|
version: 1,
|
||||||
@ -33,7 +33,7 @@ const versionedData = {meta: {version: 0}, data: {hello: 'world'}}
|
|||||||
|
|
||||||
const firstTimeState = {
|
const firstTimeState = {
|
||||||
meta: { version: 0 },
|
meta: { version: 0 },
|
||||||
data: require('../../app/scripts/first-time-state'),
|
data: require('../../../app/scripts/first-time-state'),
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Migrator', () => {
|
describe('Migrator', () => {
|
@ -6,7 +6,7 @@ var contractNamer = require(path.join(__dirname, '..', '..', 'old-ui', 'lib', 'c
|
|||||||
|
|
||||||
describe('contractNamer', function () {
|
describe('contractNamer', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.sinon = sinon.sandbox.create()
|
this.sinon = sinon.createSandbox()
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
const assert = require('assert')
|
|
||||||
const PreferencesController = require('../../app/scripts/controllers/preferences')
|
|
||||||
|
|
||||||
describe('preferences controller', function () {
|
|
||||||
let preferencesController
|
|
||||||
|
|
||||||
before(() => {
|
|
||||||
preferencesController = new PreferencesController()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('addToken', function () {
|
|
||||||
it('should add that token to its state', async function () {
|
|
||||||
const address = '0xabcdef1234567'
|
|
||||||
const symbol = 'ABBR'
|
|
||||||
const decimals = 5
|
|
||||||
|
|
||||||
await preferencesController.addToken(address, symbol, decimals)
|
|
||||||
|
|
||||||
const tokens = preferencesController.getTokens()
|
|
||||||
assert.equal(tokens.length, 1, 'one token added')
|
|
||||||
|
|
||||||
const added = tokens[0]
|
|
||||||
assert.equal(added.address, address, 'set address correctly')
|
|
||||||
assert.equal(added.symbol, symbol, 'set symbol correctly')
|
|
||||||
assert.equal(added.decimals, decimals, 'set decimals correctly')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should allow updating a token value', async function () {
|
|
||||||
const address = '0xabcdef1234567'
|
|
||||||
const symbol = 'ABBR'
|
|
||||||
const decimals = 5
|
|
||||||
|
|
||||||
await preferencesController.addToken(address, symbol, decimals)
|
|
||||||
|
|
||||||
const newDecimals = 6
|
|
||||||
await preferencesController.addToken(address, symbol, newDecimals)
|
|
||||||
|
|
||||||
const tokens = preferencesController.getTokens()
|
|
||||||
assert.equal(tokens.length, 1, 'one token added')
|
|
||||||
|
|
||||||
const added = tokens[0]
|
|
||||||
assert.equal(added.address, address, 'set address correctly')
|
|
||||||
assert.equal(added.symbol, symbol, 'set symbol correctly')
|
|
||||||
assert.equal(added.decimals, newDecimals, 'updated decimals correctly')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -10,7 +10,7 @@ var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'redu
|
|||||||
describe('#unlockMetamask(selectedAccount)', function () {
|
describe('#unlockMetamask(selectedAccount)', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
// sinon allows stubbing methods that are easily verified
|
// sinon allows stubbing methods that are easily verified
|
||||||
this.sinon = sinon.sandbox.create()
|
this.sinon = sinon.createSandbox()
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
const assert = require('assert')
|
|
||||||
const clone = require('clone')
|
|
||||||
const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper')
|
|
||||||
|
|
||||||
describe('deepCloneFromTxMeta', function () {
|
|
||||||
it('should clone deep', function () {
|
|
||||||
const input = {
|
|
||||||
foo: {
|
|
||||||
bar: {
|
|
||||||
bam: 'baz'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const output = txStateHistoryHelper.snapshotFromTxMeta(input)
|
|
||||||
assert('foo' in output, 'has a foo key')
|
|
||||||
assert('bar' in output.foo, 'has a bar key')
|
|
||||||
assert('bam' in output.foo.bar, 'has a bar key')
|
|
||||||
assert.equal(output.foo.bar.bam, 'baz', 'has a baz value')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should remove the history key', function () {
|
|
||||||
const input = { foo: 'bar', history: 'remembered' }
|
|
||||||
const output = txStateHistoryHelper.snapshotFromTxMeta(input)
|
|
||||||
assert(typeof output.history, 'undefined', 'should remove history')
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,46 +0,0 @@
|
|||||||
const assert = require('assert')
|
|
||||||
const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper')
|
|
||||||
const testVault = require('../data/v17-long-history.json')
|
|
||||||
|
|
||||||
|
|
||||||
describe('tx-state-history-helper', function () {
|
|
||||||
it('migrates history to diffs and can recover original values', function () {
|
|
||||||
testVault.data.TransactionController.transactions.forEach((tx, index) => {
|
|
||||||
const newHistory = txStateHistoryHelper.migrateFromSnapshotsToDiffs(tx.history)
|
|
||||||
newHistory.forEach((newEntry, index) => {
|
|
||||||
if (index === 0) {
|
|
||||||
assert.equal(Array.isArray(newEntry), false, 'initial history item IS NOT a json patch obj')
|
|
||||||
} else {
|
|
||||||
assert.equal(Array.isArray(newEntry), true, 'non-initial history entry IS a json patch obj')
|
|
||||||
}
|
|
||||||
const oldEntry = tx.history[index]
|
|
||||||
const historySubset = newHistory.slice(0, index + 1)
|
|
||||||
const reconstructedValue = txStateHistoryHelper.replayHistory(historySubset)
|
|
||||||
assert.deepEqual(oldEntry, reconstructedValue, 'was able to reconstruct old entry from diffs')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('replaying history does not mutate the original obj', function () {
|
|
||||||
const initialState = { test: true, message: 'hello', value: 1 }
|
|
||||||
const diff1 = [{
|
|
||||||
"op": "replace",
|
|
||||||
"path": "/message",
|
|
||||||
"value": "haay",
|
|
||||||
}]
|
|
||||||
const diff2 = [{
|
|
||||||
"op": "replace",
|
|
||||||
"path": "/value",
|
|
||||||
"value": 2,
|
|
||||||
}]
|
|
||||||
const history = [initialState, diff1, diff2]
|
|
||||||
|
|
||||||
const beforeStateSnapshot = JSON.stringify(initialState)
|
|
||||||
const latestState = txStateHistoryHelper.replayHistory(history)
|
|
||||||
const afterStateSnapshot = JSON.stringify(initialState)
|
|
||||||
|
|
||||||
assert.notEqual(initialState, latestState, 'initial state is not the same obj as the latest state')
|
|
||||||
assert.equal(beforeStateSnapshot, afterStateSnapshot, 'initial state is not modified during run')
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user