diff --git a/.circleci/config.yml b/.circleci/config.yml
index 6ed731225..939df8be5 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -15,9 +15,17 @@ workflows:
- test-lint:
requires:
- prep-deps-npm
- - test-e2e:
+ - test-deps:
requires:
- prep-deps-npm
+ - test-e2e-chrome:
+ requires:
+ - prep-deps-npm
+ - prep-build
+ - test-e2e-firefox:
+ requires:
+ - prep-deps-npm
+ - prep-deps-firefox
- prep-build
- test-unit:
requires:
@@ -43,8 +51,10 @@ workflows:
- all-tests-pass:
requires:
- test-lint
+ - test-deps
- test-unit
- - test-e2e
+ - test-e2e-chrome
+ - test-e2e-firefox
- test-integration-mascara-chrome
- test-integration-mascara-firefox
- test-integration-flat-chrome
@@ -145,7 +155,18 @@ jobs:
name: Test
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:
- image: circleci/node:8-browsers
steps:
@@ -156,7 +177,34 @@ jobs:
key: build-cache-{{ .Revision }}
- run:
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:
path: test-artifacts
destination: test-artifacts
@@ -320,3 +368,4 @@ jobs:
- run:
name: All Tests Passed
command: echo 'weew - everything passed!'
+
\ No newline at end of file
diff --git a/.nsprc b/.nsprc
index a9abc1e1e..cb37fad49 100644
--- a/.nsprc
+++ b/.nsprc
@@ -1,3 +1,6 @@
{
- "exceptions": ["https://nodesecurity.io/advisories/566"]
+ "exceptions": [
+ "https://nodesecurity.io/advisories/566",
+ "https://nodesecurity.io/advisories/157"
+ ]
}
diff --git a/.storybook/README.md b/.storybook/README.md
new file mode 100644
index 000000000..03b06d854
--- /dev/null
+++ b/.storybook/README.md
@@ -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.
diff --git a/.storybook/addons.js b/.storybook/addons.js
new file mode 100644
index 000000000..4e162fa27
--- /dev/null
+++ b/.storybook/addons.js
@@ -0,0 +1,2 @@
+import '@storybook/addon-knobs/register'
+import '@storybook/addon-actions/register'
diff --git a/.storybook/config.js b/.storybook/config.js
new file mode 100644
index 000000000..48e3997eb
--- /dev/null
+++ b/.storybook/config.js
@@ -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)
diff --git a/.storybook/decorators.js b/.storybook/decorators.js
new file mode 100644
index 000000000..7b0745ac4
--- /dev/null
+++ b/.storybook/decorators.js
@@ -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 => (
+
+ { story() }
+
+)
+
+addDecorator((story, context) => withInfo()(story)(context))
+addDecorator(withKnobs)
+addDecorator(CenterDecorator)
diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js
new file mode 100644
index 000000000..bd66954f3
--- /dev/null
+++ b/.storybook/webpack.config.js
@@ -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'),
+ },
+ },
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e2b481de..0bb50fd5b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,11 +2,14 @@
## Current Master
+## 4.6.0 Thu Apr 26 2018
+
- Correctly format currency conversion for locally selected preferred currency.
- Improved performance of 3D fox logo.
- Fetch token prices based on contract address, not symbol
- Fix bug that prevents setting language locale in settings.
- Show checksum addresses throughout the UI
+- Allow transactions with a 0 gwei gas price
## 4.5.5 Fri Apr 06 2018
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5d2e5207b..8517eed70 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -12,12 +12,14 @@ For any new programmatic functionality, we like unit tests when possible, so if
### PR Format
-We use [waffle](https://waffle.io/) for project management, and it will automatically keep us organized if you do one simple thing:
-
If this PR closes the issue, add the line `Fixes #$ISSUE_NUMBER`. Ex. For closing issue 418, include the line `Fixes #418`.
If it doesn't close the issue but addresses it partially, just include a reference to the issue number, like `#418`.
+Submit your PR against the `develop` branch. This is where we merge new features so they get some time to receive extra testing before being pushed to `master` for production.
+
+If your PR is a hot-fix that needs to be published urgently, you may submit a PR against the `master` branch, but this PR will receive tighter scrutiny before merging.
+
## Before Merging
Make sure you get a `:thumbsup`, `:+1`, or `LGTM` from another collaborator before merging.
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 3b20ab49a..214355589 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -98,6 +98,9 @@
"clickCopy": {
"message": "Click to Copy"
},
+ "close": {
+ "message": "Close"
+ },
"confirm": {
"message": "Confirm"
},
@@ -259,6 +262,9 @@
"enterPasswordConfirm": {
"message": "Enter your password to confirm"
},
+ "enterPasswordContinue": {
+ "message": "Enter password to continue"
+ },
"passwordNotLongEnough": {
"message": "Password not long enough"
},
@@ -331,6 +337,9 @@
"gasPriceRequired": {
"message": "Gas Price Required"
},
+ "generatingTransaction": {
+ "message": "Generating transaction"
+ },
"getEther": {
"message": "Get Ether"
},
@@ -384,6 +393,9 @@
"message": "Imported",
"description": "status showing that an account has been fully loaded into the keyring"
},
+ "importUsingSeed": {
+ "message": "Import using account seed phrase"
+ },
"infoHelp": {
"message": "Info & Help"
},
@@ -476,6 +488,9 @@
"metamaskDescription": {
"message": "MetaMask is a secure identity vault for Ethereum."
},
+ "metamaskSeedWords": {
+ "message": "MetaMask Seed Words"
+ },
"min": {
"message": "Minimum"
},
@@ -549,6 +564,9 @@
"message": "or",
"description": "choice between creating or importing a new account"
},
+ "password": {
+ "message": "Password"
+ },
"passwordCorrect": {
"message": "Please make sure your password is correct."
},
@@ -617,7 +635,7 @@
"message": "Reset Account"
},
"restoreFromSeed": {
- "message": "Restore from seed phrase"
+ "message": "Restore account?"
},
"restoreVault": {
"message": "Restore Vault"
@@ -634,8 +652,17 @@
"revealSeedWords": {
"message": "Reveal Seed Words"
},
+ "revealSeedWordsTitle": {
+ "message": "Seed Phrase"
+ },
+ "revealSeedWordsDescription": {
+ "message": "If you ever change browsers or move computers, you will need this seed phrase to access your accounts. Save them somewhere safe and secret."
+ },
+ "revealSeedWordsWarningTitle": {
+ "message": "DO NOT share this phrase with anyone!"
+ },
"revealSeedWordsWarning": {
- "message": "Do not recover your seed words in a public place! These words can be used to steal all your accounts."
+ "message": "These words can be used to steal all your accounts."
},
"revert": {
"message": "Revert"
@@ -677,6 +704,9 @@
"reprice_subtitle": {
"message": "Increase your gas price to attempt to overwrite and speed up your transaction"
},
+ "saveAsCsvFile": {
+ "message": "Save as CSV File"
+ },
"saveAsFile": {
"message": "Save as File",
"description": "Account export process"
@@ -869,6 +899,9 @@
"unknownNetworkId": {
"message": "Unknown network ID"
},
+ "unlockMessage": {
+ "message": "The decentralized web awaits"
+ },
"uriErrorMsg": {
"message": "URIs require the appropriate HTTP/HTTPS prefix."
},
@@ -897,6 +930,9 @@
"warning": {
"message": "Warning"
},
+ "welcomeBack": {
+ "message": "Welcome Back!"
+ },
"welcomeBeta": {
"message": "Welcome to MetaMask Beta"
},
@@ -909,7 +945,7 @@
"youSign": {
"message": "You are signing"
},
- "generatingTransaction": {
- "message": "Generating transaction"
+ "yourPrivateSeedPhrase": {
+ "message": "Your private seed phrase"
}
}
diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json
index b089f3476..25bd0bcbb 100644
--- a/app/_locales/sl/messages.json
+++ b/app/_locales/sl/messages.json
@@ -181,7 +181,7 @@
"message": "DEN je vaša šifrirana shramba v MetaMasku."
},
"deposit": {
- "message": "Vplačilo"
+ "message": "Vplačaj"
},
"depositBTC": {
"message": "Vplačajte vaš BTC na spodnji naslov:"
@@ -507,10 +507,10 @@
"message": "Ni se začelo"
},
"oldUI": {
- "message": "Starejši uporabniški vmesnik"
+ "message": "Star UI"
},
"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": {
"message": "ali",
@@ -759,7 +759,7 @@
"message": "Vpišite vaše geslo"
},
"uiWelcome": {
- "message": "Dobrodošli v novem uporabniškem vmesniku (Beta)"
+ "message": "Dobrodošli v nov UI (Beta)"
},
"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."
diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json
index 203ab1923..241ea948d 100644
--- a/app/_locales/zh_CN/messages.json
+++ b/app/_locales/zh_CN/messages.json
@@ -14,9 +14,15 @@
"address": {
"message": "地址"
},
+ "addCustomToken": {
+ "message": "添加自定义代币"
+ },
"addToken": {
"message": "添加代币"
},
+ "addTokens": {
+ "message": "添加代币"
+ },
"amount": {
"message": "数量"
},
@@ -31,9 +37,15 @@
"message": "MetaMask",
"description": "The name of the application"
},
+ "approved": {
+ "message": "批准"
+ },
"attemptingConnect": {
"message": "正在尝试连接区块链。"
},
+ "attributions": {
+ "message": "来源"
+ },
"available": {
"message": "可用"
},
@@ -43,6 +55,9 @@
"balance": {
"message": "余额:"
},
+ "balances": {
+ "message": "代币余额"
+ },
"balanceIsInsufficientGas": {
"message": "当前余额不足以支付 Gas"
},
@@ -53,9 +68,15 @@
"message": "必须大于等于 $1 并且小于等于 $2 。",
"description": "helper for inputting hex as decimal input"
},
+ "blockiesIdenticon": {
+ "message": "使用区块Identicon"
+ },
"borrowDharma": {
"message": "Borrow With Dharma (Beta)"
},
+ "builtInCalifornia": {
+ "message": "MetaMask在加利福尼亚设计和制造。"
+ },
"buy": {
"message": "购买"
},
@@ -65,15 +86,27 @@
"buyCoinbaseExplainer": {
"message": "Coinbase 是世界上最流行的买卖比特币,以太币和莱特币的交易所。"
},
+ "ok": {
+ "message": "确认"
+ },
"cancel": {
"message": "取消"
},
+ "classicInterface": {
+ "message": "使用经典接口"
+ },
"clickCopy": {
"message": "点击复制"
},
+ "close": {
+ "message": "关闭"
+ },
"confirm": {
"message": "确认"
},
+ "confirmed": {
+ "message": "确认"
+ },
"confirmContract": {
"message": "确认合约"
},
@@ -83,6 +116,9 @@
"confirmTransaction": {
"message": "确认交易"
},
+ "continue": {
+ "message": "继续"
+ },
"continueToCoinbase": {
"message": "继续访问 Coinbase"
},
@@ -99,7 +135,10 @@
"message": "已复制到剪贴板"
},
"copiedExclamation": {
- "message": "已复制!"
+ "message": "已复制"
+ },
+ "copiedSafe": {
+ "message": "我已将它复制保存到某个安全的地方"
},
"copy": {
"message": "复制"
@@ -126,15 +165,30 @@
"message": "加密",
"description": "Exchange type (cryptocurrencies)"
},
+ "currentConversion": {
+ "message": "当前汇率"
+ },
+ "currentNetwork": {
+ "message": "当前网络"
+ },
"customGas": {
"message": "自定义 Gas"
},
+ "customToken": {
+ "message": "自定义代币"
+ },
"customize": {
"message": "自定义"
},
"customRPC": {
"message": "自定义 RPC"
},
+ "decimalsMustZerotoTen": {
+ "message": "小数位最小为0并且不超过36位."
+ },
+ "decimal": {
+ "message": "精确小数点"
+ },
"defaultNetwork": {
"message": "默认以太坊交易网络为主网。"
},
@@ -184,18 +238,39 @@
"done": {
"message": "完成"
},
+ "downloadStateLogs": {
+ "message": "下载日志"
+ },
+ "dropped": {
+ "message": "丢弃"
+ },
"edit": {
"message": "编辑"
},
"editAccountName": {
"message": "编辑账户名称"
},
+ "emailUs": {
+ "message": "联系我们"
+ },
"encryptNewDen": {
"message": "加密你的新 DEN"
},
"enterPassword": {
"message": "请输入密码"
},
+ "enterPasswordConfirm": {
+ "message": "请输入密码以确认"
+ },
+ "enterPasswordContinue": {
+ "message": "请输入密码以继续"
+ },
+ "passwordNotLongEnough": {
+ "message": "密码长度不足"
+ },
+ "passwordsDontMatch": {
+ "message": "密码不匹配"
+ },
"etherscanView": {
"message": "在 Etherscan 上查看账户"
},
@@ -219,9 +294,15 @@
"message": "文件导入失败? 点击这里!",
"description": "Helps user import their account from a JSON file"
},
+ "followTwitter": {
+ "message": "关注我们的Twitter"
+ },
"from": {
"message": "来自"
},
+ "fromToSame": {
+ "message": "发送和接受地址不能相同"
+ },
"fromShapeShift": {
"message": "来自 ShapeShift"
},
@@ -244,6 +325,9 @@
"gasLimitTooLow": {
"message": "Gas Limit 至少要 21000"
},
+ "generatingSeed": {
+ "message": "生成密钥中..."
+ },
"gasPrice": {
"message": "Gas Price (GWEI)"
},
@@ -253,6 +337,9 @@
"gasPriceRequired": {
"message": "Gas Price 必填"
},
+ "generatingTransaction": {
+ "message": "生成 交易"
+ },
"getEther": {
"message": "获取 Ether"
},
@@ -268,6 +355,9 @@
"message": "这里",
"description": "as in -click here- for more information (goes with troubleTokenBalances)"
},
+ "hereList": {
+ "message": "Here's a list!!!!"
+ },
"hide": {
"message": "隐藏"
},
@@ -280,6 +370,9 @@
"howToDeposit": {
"message": "你想怎样转入 Ether?"
},
+ "holdEther": {
+ "message": "它允许你保存ether和代币,并作为你使用Dapp的桥梁."
+ },
"import": {
"message": "导入",
"description": "Button to import an account from a selected file"
@@ -287,6 +380,9 @@
"importAccount": {
"message": "导入账户"
},
+ "importAccountMsg": {
+ "message":" Imported accounts will not be associated with your originally created MetaMask account seedphrase. Learn more about imported accounts "
+ },
"importAnAccount": {
"message": "导入一个账户"
},
@@ -294,46 +390,82 @@
"message": "导入存在的 DEN"
},
"imported": {
- "message": "已导入私钥",
+ "message": "已导入",
"description": "status showing that an account has been fully loaded into the keyring"
},
"infoHelp": {
"message": "信息 & 帮助"
},
+ "insufficientFunds": {
+ "message": "余额不足."
+ },
+ "insufficientTokens": {
+ "message": "代币余额不足."
+ },
"invalidAddress": {
- "message": "错误的地址"
+ "message": "无效地址"
+ },
+ "invalidAddressRecipient": {
+ "message": "收款地址不合法"
},
"invalidGasParams": {
- "message": "错误的 Gas 参数"
+ "message": "无效 Gas 参数"
},
"invalidInput": {
- "message": "错误的输入。"
+ "message": "无效输入."
},
"invalidRequest": {
"message": "无效请求"
},
+ "invalidRPC": {
+ "message": "无效 RPC URI"
+ },
+ "jsonFail": {
+ "message": "Something went wrong. Please make sure your JSON file is properly formatted."
+ },
"jsonFile": {
"message": "JSON 文件",
"description": "format for importing an account"
},
+ "keepTrackTokens": {
+ "message": "Keep track of the tokens you’ve bought with your MetaMask account."
+ },
"kovan": {
"message": "Kovan 测试网络"
},
+ "knowledgeDataBase": {
+ "message": "浏览我们的知识库"
+ },
+ "max": {
+ "message": "最大"
+ },
+ "learnMore": {
+ "message": "查看更多."
+ },
"lessThanMax": {
- "message": "必须小于等于 $1.",
+ "message": "必须小于或等于 $1.",
"description": "helper for inputting hex as decimal input"
},
+ "likeToAddTokens": {
+ "message": "你想添加这些代币吗?"
+ },
+ "links": {
+ "message": "链接"
+ },
"limit": {
- "message": "限定"
+ "message": "限制"
},
"loading": {
- "message": "加载..."
+ "message": "加载中..."
},
"loadingTokens": {
- "message": "加载代币..."
+ "message": "加载代币中..."
},
"localhost": {
- "message": "本地主机 8545"
+ "message": "Localhost 8545"
+ },
+ "login": {
+ "message": "登录"
},
"logout": {
"message": "登出"
@@ -341,17 +473,29 @@
"loose": {
"message": "疏松"
},
+ "loweCaseWords": {
+ "message": "助记词只有小写字符"
+ },
"mainnet": {
"message": "以太坊主网络"
},
"message": {
"message": "消息"
},
+ "metamaskDescription": {
+ "message": "MetaMask is a secure identity vault for Ethereum."
+ },
+ "metamaskSeedWords": {
+ "message": "MetaMask 助记词"
+ },
"min": {
"message": "最小"
},
"myAccounts": {
- "message": "我的账户"
+ "message": "My Accounts"
+ },
+ "mustSelectOne": {
+ "message": "至少选择一种代币."
},
"needEtherInWallet": {
"message": "使用 MetaMask 与 DAPP 交互,需要你的钱包里有 Ether。"
@@ -361,9 +505,12 @@
"description": "User is important an account and needs to add a file to continue"
},
"needImportPassword": {
- "message": "必须为已选择的文件输入密码。",
+ "message": "必须为已选择的文件输入密码。",
"description": "Password and file needed to import an account"
},
+ "negativeETH": {
+ "message": "Can not send negative amounts of ETH."
+ },
"networks": {
"message": "网络"
},
@@ -383,8 +530,11 @@
"newRecipient": {
"message": "新收款人"
},
+ "newRPC": {
+ "message": "新 RPC URL"
+ },
"next": {
- "message": "下一个"
+ "message": "下一步"
},
"noAddressForName": {
"message": "此 ENS 名字还没有指定地址。"
@@ -405,12 +555,18 @@
"message": "旧版界面"
},
"oldUIMessage": {
- "message": "你已经切换到旧版界面。 你可以通过右上方下拉菜单中的选项切换回新的用户界面。"
+ "message": "你已经切换到旧版界面。 你可以通过右上方下拉菜单中的选项切换回新的用户界面。"
},
"or": {
"message": "或",
"description": "choice between creating or importing a new account"
},
+ "password": {
+ "message": "密码"
+ },
+ "passwordCorrect": {
+ "message": "Please make sure your password is correct."
+ },
"passwordMismatch": {
"message": "密码不匹配",
"description": "in password creation process, the two new password fields did not match"
@@ -426,15 +582,24 @@
"pasteSeed": {
"message": "请粘贴你的助记词!"
},
+ "personalAddressDetected": {
+ "message": "检测到个人地址。请输入代币合约地址。"
+ },
"pleaseReviewTransaction": {
"message": "请检查你的交易。"
},
+ "popularTokens": {
+ "message": "常用代币"
+ },
+ "privacyMsg": {
+ "message": "隐私政策"
+ },
"privateKey": {
"message": "私钥",
"description": "select this type of file to use to import an account"
},
"privateKeyWarning": {
- "message": "注意:永远不要公开这个私钥。任何拥有你的私钥的人都可以窃取你帐户中的任何资产。"
+ "message": "注意:永远不要公开这个私钥。任何拥有你的私钥的人都可以窃取你帐户中的任何资产。"
},
"privateNetwork": {
"message": "私有网络"
@@ -443,11 +608,14 @@
"message": "显示二维码"
},
"readdToken": {
- "message": "之后你还可以通过帐户选项菜单中的“添加代币”来添加此代币。"
+ "message": "之后你还可以通过帐户选项菜单中的“添加代币”来添加此代币。"
},
"readMore": {
"message": "了解更多。"
},
+ "readMore2": {
+ "message": "了解更多。"
+ },
"receive": {
"message": "接收"
},
@@ -460,12 +628,39 @@
"rejected": {
"message": "拒绝"
},
+ "resetAccount": {
+ "message": "重设账户"
+ },
+ "restoreFromSeed": {
+ "message": "从助记词还原"
+ },
+ "restoreVault": {
+ "message": "还原保险柜"
+ },
"required": {
"message": "必填"
},
"retryWithMoreGas": {
"message": "使用更高的 Gas Price 重试"
},
+ "walletSeed": {
+ "message": "钱包助记词"
+ },
+ "revealSeedWords": {
+ "message": "显示助记词"
+ },
+ "revealSeedWordsTitle": {
+ "message": "助记词"
+ },
+ "revealSeedWordsDescription": {
+ "message": "如果您更换浏览器或计算机,则需要使用此助记词访问您的帐户。请将它们保存在安全秘密的地方。"
+ },
+ "revealSeedWordsWarningTitle": {
+ "message": "不要对任何人展示助记词!"
+ },
+ "revealSeedWordsWarning": {
+ "message": "助记词可以用来窃取您的所有帐户."
+ },
"revert": {
"message": "还原"
},
@@ -475,6 +670,24 @@
"ropsten": {
"message": "Ropsten 测试网络"
},
+ "currentRpc": {
+ "message": "当前 RPC"
+ },
+ "connectingToMainnet": {
+ "message": "正在连接到以太坊主网"
+ },
+ "connectingToRopsten": {
+ "message": "正在连接到Ropsten测试网络"
+ },
+ "connectingToKovan": {
+ "message": "正在连接到Kovan测试网络"
+ },
+ "connectingToRinkeby": {
+ "message": "正在连接到Rinkeby测试网络"
+ },
+ "connectingToUnknown": {
+ "message": "正在连接到未知网络"
+ },
"sampleAccountName": {
"message": "例如:我的账户",
"description": "Help user understand concept of adding a human-readable name to their account"
@@ -482,25 +695,70 @@
"save": {
"message": "保存"
},
+ "reprice_title": {
+ "message": "重新出价交易"
+ },
+ "reprice_subtitle": {
+ "message": "提高 GAS 价格尝试覆盖并加速交易"
+ },
+ "saveAsCsvFile": {
+ "message": "另存为CSV文件"
+ },
"saveAsFile": {
"message": "保存文件",
"description": "Account export process"
},
+ "saveSeedAsFile": {
+ "message": "保存助记词为文件"
+ },
+ "search": {
+ "message": "搜索"
+ },
+ "secretPhrase": {
+ "message": "输入12位助记词以恢复金库."
+ },
+ "newPassword8Chars": {
+ "message": "新密码(至少8位)"
+ },
+ "seedPhraseReq": {
+ "message": "助记词为12个单词"
+ },
+ "select": {
+ "message": "选择"
+ },
+ "selectCurrency": {
+ "message": "选择货币"
+ },
"selectService": {
"message": "选择服务"
},
+ "selectType": {
+ "message": "选择类型"
+ },
"send": {
"message": "发送"
},
+ "sendETH": {
+ "message": "发送 ETH"
+ },
"sendTokens": {
- "message": "发送代币"
+ "message": "发送 代币"
+ },
+ "onlySendToEtherAddress": {
+ "message": "只发送 ETH 给一个以太坊地址"
+ },
+ "searchTokens": {
+ "message": "搜索代币"
},
"sendTokensAnywhere": {
- "message": "发送代币给拥有以太坊账户的任何人"
+ "message": "将代币发送给拥有以太坊地址的任何人"
},
"settings": {
"message": "设置"
},
+ "info": {
+ "message": "信息"
+ },
"shapeshiftBuy": {
"message": "使用 Shapeshift 购买"
},
@@ -513,6 +771,9 @@
"sign": {
"message": "签名"
},
+ "signed": {
+ "message": "已签名"
+ },
"signMessage": {
"message": "签署消息"
},
@@ -525,15 +786,39 @@
"sigRequested": {
"message": "签名已请求"
},
+ "spaceBetween": {
+ "message": "单词之间只能有一个空格"
+ },
"status": {
"message": "状态"
},
+ "stateLogs": {
+ "message": "状态日志"
+ },
+ "stateLogsDescription": {
+ "message": "状态日志包含您的账户地址和已发送的交易。"
+ },
+ "stateLogError": {
+ "message": "检索状态日志时出错。"
+ },
"submit": {
"message": "提交"
},
+ "submitted": {
+ "message": "已提交"
+ },
+ "supportCenter": {
+ "message": "访问我们的支持中心"
+ },
+ "symbolBetweenZeroTen": {
+ "message": "符号应该有0-10个字符."
+ },
"takesTooLong": {
"message": "花费太长时间?"
},
+ "terms": {
+ "message": "使用条款"
+ },
"testFaucet": {
"message": "测试水管"
},
@@ -544,33 +829,60 @@
"message": "$1 ETH 通过 ShapeShift",
"description": "system will fill in deposit type in start of message"
},
+ "tokenAddress": {
+ "message": "代币地址"
+ },
+ "tokenAlreadyAdded": {
+ "message": "代币已经被添加."
+ },
"tokenBalance": {
"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": {
"message": "总量"
},
+ "transactions": {
+ "message": "交易"
+ },
+ "transactionError": {
+ "message": "交易出错. 合约代码执行异常."
+ },
"transactionMemo": {
- "message": "交易备注 (可选)"
+ "message": "交易备注(可选)"
},
"transactionNumber": {
- "message": "交易号"
+ "message": "交易 number"
},
"transfers": {
- "message": "Transfers"
+ "message": "交易"
},
"troubleTokenBalances": {
- "message": "无法加载代币余额。你可以再这里查看 ",
+ "message": "我们无法加载您的代币余额。你可以查看它们",
"description": "Followed by a link (here) to view token balances"
},
+ "twelveWords": {
+ "message": "这12个单词是恢复MetaMask帐户的唯一方法。.\n将它们存放在安全和秘密的地方。."
+ },
"typePassword": {
- "message": "请输入密码"
+ "message": "输入你的密码"
},
"uiWelcome": {
"message": "欢迎使用新版界面 (Beta)"
},
"uiWelcomeMessage": {
- "message": "你现在正在使用新的 Metamask 界面。 尝试发送代币等新功能,有任何问题请告知我们。"
+ "message": "你现在正在使用新的 Metamask 界面。 尝试发送代币等新功能,有任何问题请告知我们。"
+ },
+ "unapproved": {
+ "message": "未批准"
},
"unavailable": {
"message": "不可用"
@@ -582,7 +894,10 @@
"message": "未知私有网络"
},
"unknownNetworkId": {
- "message": "未知网络 ID"
+ "message": "未知网络ID"
+ },
+ "uriErrorMsg": {
+ "message": "URIs require the appropriate HTTP/HTTPS prefix."
},
"usaOnly": {
"message": "只限于美国",
@@ -591,12 +906,27 @@
"usedByClients": {
"message": "可用于各种不同的客户端"
},
+ "useOldUI": {
+ "message": "使用旧版 UI"
+ },
+ "validFileImport": {
+ "message": "您必须选择一个有效的文件进行导入."
+ },
+ "vaultCreated": {
+ "message": "已创建保险库"
+ },
"viewAccount": {
"message": "查看账户"
},
+ "visitWebSite": {
+ "message": "访问我们的网站"
+ },
"warning": {
"message": "警告"
},
+ "welcomeBeta": {
+ "message": "欢迎使用 MetaMask 测试版"
+ },
"whatsThis": {
"message": "这是什么?"
},
@@ -605,5 +935,8 @@
},
"youSign": {
"message": "正在签名"
+ },
+ "yourPrivateSeedPhrase": {
+ "message": "你的私有助记词"
}
}
diff --git a/app/images/copy-to-clipboard.svg b/app/images/copy-to-clipboard.svg
new file mode 100644
index 000000000..c67c2aa84
--- /dev/null
+++ b/app/images/copy-to-clipboard.svg
@@ -0,0 +1,24 @@
+
+
\ No newline at end of file
diff --git a/app/images/download.svg b/app/images/download.svg
index 137a1190e..b55066414 100644
--- a/app/images/download.svg
+++ b/app/images/download.svg
@@ -1,15 +1,26 @@
-
-
-
-
+
+
\ No newline at end of file
diff --git a/app/images/warning.svg b/app/images/warning.svg
new file mode 100644
index 000000000..9c8d697d7
--- /dev/null
+++ b/app/images/warning.svg
@@ -0,0 +1,22 @@
+
+
\ No newline at end of file
diff --git a/app/manifest.json b/app/manifest.json
index dc46f1ca4..3e5eed205 100644
--- a/app/manifest.json
+++ b/app/manifest.json
@@ -1,7 +1,7 @@
{
"name": "__MSG_appName__",
"short_name": "__MSG_appName__",
- "version": "4.5.5",
+ "version": "4.6.0",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "__MSG_appDescription__",
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 38b871bb5..69d549c85 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -261,7 +261,11 @@ function setupController (initState, initLangCode) {
controller.txController.on(`tx:status-update`, (txId, status) => {
if (status !== 'failed') return
const txMeta = controller.txController.txStateManager.getTx(txId)
- reportFailedTxToSentry({ raven, txMeta })
+ try {
+ reportFailedTxToSentry({ raven, txMeta })
+ } catch (e) {
+ console.error(e)
+ }
})
// setup state persistence
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index dbf1c6d4c..ddf1a9432 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -174,6 +174,7 @@ function blacklistedDomainCheck () {
'uscourts.gov',
'dropbox.com',
'webbyawards.com',
+ 'cdn.shopify.com/s/javascripts/tricorder/xtld-read-only-frame.html',
]
var currentUrl = window.location.href
var currentRegex
diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js
index f83f294cc..86619fce1 100644
--- a/app/scripts/controllers/balance.js
+++ b/app/scripts/controllers/balance.js
@@ -4,6 +4,24 @@ const BN = require('ethereumjs-util').BN
class BalanceController {
+ /**
+ * Controller responsible for storing and updating an account's balance.
+ *
+ * @typedef {Object} BalanceController
+ * @param {Object} opts Initialize various properties of the class.
+ * @property {string} address A base 16 hex string. The account address which has the balance managed by this
+ * BalanceController.
+ * @property {AccountTracker} accountTracker Stores and updates the users accounts
+ * for which this BalanceController manages balance.
+ * @property {TransactionController} txController Stores, tracks and manages transactions. Here used to create a listener for
+ * transaction updates.
+ * @property {BlockTracker} blockTracker Tracks updates to blocks. On new blocks, this BalanceController updates its balance
+ * @property {Object} store The store for the ethBalance
+ * @property {string} store.ethBalance A base 16 hex string. The balance for the current account.
+ * @property {PendingBalanceCalculator} balanceCalc Used to calculate the accounts balance with possible pending
+ * transaction costs taken into account.
+ *
+ */
constructor (opts = {}) {
this._validateParams(opts)
const { address, accountTracker, txController, blockTracker } = opts
@@ -26,6 +44,11 @@ class BalanceController {
this._registerUpdates()
}
+ /**
+ * Updates the ethBalance property to the current pending balance
+ *
+ * @returns {Promise} Promises undefined
+ */
async updateBalance () {
const balance = await this.balanceCalc.getBalance()
this.store.updateState({
@@ -33,6 +56,15 @@ class BalanceController {
})
}
+ /**
+ * Sets up listeners and subscriptions which should trigger an update of ethBalance. These updates include:
+ * - when a transaction changes state to 'submitted', 'confirmed' or 'failed'
+ * - when the current account changes (i.e. a new account is selected)
+ * - when there is a block update
+ *
+ * @private
+ *
+ */
_registerUpdates () {
const update = this.updateBalance.bind(this)
@@ -51,6 +83,14 @@ class BalanceController {
this.blockTracker.on('block', update)
}
+ /**
+ * Gets the balance, as a base 16 hex string, of the account at this BalanceController's current address.
+ * If the current account has no balance, returns undefined.
+ *
+ * @returns {Promise} Promises a BN with a value equal to the balance of the current account, or undefined
+ * if the current account has no balance
+ *
+ */
async _getBalance () {
const { accounts } = this.accountTracker.store.getState()
const entry = accounts[this.address]
@@ -58,6 +98,14 @@ class BalanceController {
return balance ? new BN(balance.substring(2), 16) : undefined
}
+ /**
+ * Gets the pending transactions (i.e. those with a 'submitted' status). These are accessed from the
+ * TransactionController passed to this BalanceController during construction.
+ *
+ * @private
+ * @returns {Promise} Promises an array of transaction objects.
+ *
+ */
async _getPendingTransactions () {
const pending = this.txController.getFilteredTxList({
from: this.address,
@@ -67,6 +115,14 @@ class BalanceController {
return pending
}
+ /**
+ * Validates that the passed options have all required properties.
+ *
+ * @param {Object} opts The options object to validate
+ * @throws {string} Throw a custom error indicating that address, accountTracker, txController and blockTracker are
+ * missing and at least one is required
+ *
+ */
_validateParams (opts) {
const { address, accountTracker, txController, blockTracker } = opts
if (!address || !accountTracker || !txController || !blockTracker) {
diff --git a/app/scripts/controllers/blacklist.js b/app/scripts/controllers/blacklist.js
index d965f80b8..f100c4525 100644
--- a/app/scripts/controllers/blacklist.js
+++ b/app/scripts/controllers/blacklist.js
@@ -10,6 +10,22 @@ const POLLING_INTERVAL = 4 * 60 * 1000
class BlacklistController {
+ /**
+ * Responsible for polling for and storing an up to date 'eth-phishing-detect' config.json file, while
+ * exposing a method that can check whether a given url is a phishing attempt. The 'eth-phishing-detect'
+ * config.json file contains a fuzzylist, whitelist and blacklist.
+ *
+ *
+ * @typedef {Object} BlacklistController
+ * @param {object} opts Overrides the defaults for the initial state of this.store
+ * @property {object} store The the store of the current phishing config
+ * @property {object} store.phishing Contains fuzzylist, whitelist and blacklist arrays. @see
+ * {@link https://github.com/MetaMask/eth-phishing-detect/blob/master/src/config.json}
+ * @property {object} _phishingDetector The PhishingDetector instantiated by passing store.phishing to
+ * PhishingDetector.
+ * @property {object} _phishingUpdateIntervalRef Id of the interval created to periodically update the blacklist
+ *
+ */
constructor (opts = {}) {
const initState = extend({
phishing: PHISHING_DETECTION_CONFIG,
@@ -22,16 +38,28 @@ class BlacklistController {
this._phishingUpdateIntervalRef = null
}
- //
- // PUBLIC METHODS
- //
-
+ /**
+ * Given a url, returns the result of checking if that url is in the store.phishing blacklist
+ *
+ * @param {string} hostname The hostname portion of a url; the one that will be checked against the white and
+ * blacklists of store.phishing
+ * @returns {boolean} Whether or not the passed hostname is on our phishing blacklist
+ *
+ */
checkForPhishing (hostname) {
if (!hostname) return false
const { result } = this._phishingDetector.check(hostname)
return result
}
+ /**
+ * Queries `https://api.infura.io/v2/blacklist` for an updated blacklist config. This is passed to this._phishingDetector
+ * to update our phishing detector instance, and is updated in the store. The new phishing config is returned
+ *
+ *
+ * @returns {Promise