diff --git a/.circleci/config.yml b/.circleci/config.yml index 0ad5cdf53..7063f3113 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -101,29 +101,31 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ .Revision }} + keys: + - dependency-cache-{{ checksum "package-lock.json" }} + # fallback to using the latest cache if no exact match is found + - dependency-cache- - run: - name: Install deps via npm - command: npm install + name: Install npm 6 + deps via npm + command: | + sudo npm install -g npm@6.1.0 && npm install - save_cache: key: dependency-cache-{{ checksum "package-lock.json" }} paths: - node_modules - - save_cache: - key: dependency-cache-{{ .Revision }} - paths: - - node_modules prep-deps-firefox: docker: - image: circleci/node:8.11.3-browsers steps: - checkout + - restore_cache: + key: dependency-cache-firefox- - run: - name: Download Firefox + name: Download Firefox If needed command: ./.circleci/scripts/firefox-download.sh - save_cache: - key: dependency-cache-firefox-{{ .Revision }} + key: dependency-cache-firefox- paths: - firefox @@ -133,7 +135,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ .Revision }} + key: dependency-cache-{{ checksum "package-lock.json" }} - run: name: build:dist command: npm run dist @@ -152,7 +154,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ .Revision }} + key: dependency-cache-{{ checksum "package-lock.json" }} - run: name: build:dist command: npm run doc @@ -167,7 +169,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ .Revision }} + key: dependency-cache-{{ checksum "package-lock.json" }} - run: name: Get Scss Cache key # this allows us to checksum against a whole directory @@ -186,7 +188,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ .Revision }} + key: dependency-cache-{{ checksum "package-lock.json" }} - run: name: Test command: npm run lint @@ -197,7 +199,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ .Revision }} + key: dependency-cache-{{ checksum "package-lock.json" }} - run: name: Test command: npx nsp check @@ -208,7 +210,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ .Revision }} + key: dependency-cache-{{ checksum "package-lock.json" }} - restore_cache: key: build-cache-{{ .Revision }} - run: @@ -224,12 +226,12 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-firefox-{{ .Revision }} + key: dependency-cache-firefox- - run: name: Install firefox command: ./.circleci/scripts/firefox-install.sh - restore_cache: - key: dependency-cache-{{ .Revision }} + key: dependency-cache-{{ checksum "package-lock.json" }} - restore_cache: key: build-cache-{{ .Revision }} - run: @@ -245,7 +247,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ .Revision }} + key: dependency-cache-{{ checksum "package-lock.json" }} - restore_cache: key: build-cache-{{ .Revision }} - run: @@ -261,12 +263,12 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-firefox-{{ .Revision }} + key: dependency-cache-firefox- - run: name: Install firefox command: ./.circleci/scripts/firefox-install.sh - restore_cache: - key: dependency-cache-{{ .Revision }} + key: dependency-cache-{{ checksum "package-lock.json" }} - restore_cache: key: build-cache-{{ .Revision }} - run: @@ -282,7 +284,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ .Revision }} + key: dependency-cache-{{ checksum "package-lock.json" }} - restore_cache: key: build-cache-{{ .Revision }} - run: @@ -299,7 +301,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ .Revision }} + key: dependency-cache-{{ checksum "package-lock.json" }} - restore_cache: key: build-cache-{{ .Revision }} - restore_cache: @@ -326,7 +328,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ .Revision }} + key: dependency-cache-{{ checksum "package-lock.json" }} - restore_cache: key: build-cache-{{ .Revision }} - restore_cache: @@ -349,7 +351,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ .Revision }} + key: dependency-cache-{{ checksum "package-lock.json" }} - run: name: test:coverage command: npm run test:coverage @@ -362,12 +364,12 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-firefox-{{ .Revision }} + key: dependency-cache-firefox- - run: name: Install firefox command: ./.circleci/scripts/firefox-install.sh - restore_cache: - key: dependency-cache-{{ .Revision }} + key: dependency-cache-{{ checksum "package-lock.json" }} - run: name: Get Scss Cache key # this allows us to checksum against a whole directory @@ -386,7 +388,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ .Revision }} + key: dependency-cache-{{ checksum "package-lock.json" }} - run: name: Get Scss Cache key # this allows us to checksum against a whole directory @@ -405,12 +407,12 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-firefox-{{ .Revision }} + key: dependency-cache-firefox- - run: name: Install firefox command: ./.circleci/scripts/firefox-install.sh - restore_cache: - key: dependency-cache-{{ .Revision }} + key: dependency-cache-{{ checksum "package-lock.json" }} - run: name: Get Scss Cache key # this allows us to checksum against a whole directory @@ -429,7 +431,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ .Revision }} + key: dependency-cache-{{ checksum "package-lock.json" }} - run: name: Get Scss Cache key # this allows us to checksum against a whole directory diff --git a/.circleci/scripts/firefox-download.sh b/.circleci/scripts/firefox-download.sh index c63e8c3df..64f0c74e3 100755 --- a/.circleci/scripts/firefox-download.sh +++ b/.circleci/scripts/firefox-download.sh @@ -1,6 +1,13 @@ #!/usr/bin/env bash - -echo "Downloading firefox..." -wget https://ftp.mozilla.org/pub/firefox/releases/58.0/linux-x86_64/en-US/firefox-58.0.tar.bz2 \ -&& tar xjf firefox-58.0.tar.bz2 -echo "firefox download complete" +echo "Checking if firefox was already downloaded" +if [ -d "firefox" ] +then + echo "Firefox found. No need to download" +else + FIREFOX_VERSION="61.0.1" + FIREFOX_BINARY="firefox-$FIREFOX_VERSION.tar.bz2" + echo "Downloading firefox..." + wget "https://ftp.mozilla.org/pub/firefox/releases/$FIREFOX_VERSION/linux-x86_64/en-US/$FIREFOX_BINARY" \ + && tar xjf "$FIREFOX_BINARY" + echo "firefox download complete" +fi diff --git a/.circleci/scripts/firefox-install.sh b/.circleci/scripts/firefox-install.sh index 589bcbbb5..1c60f4de9 100755 --- a/.circleci/scripts/firefox-install.sh +++ b/.circleci/scripts/firefox-install.sh @@ -2,7 +2,7 @@ echo "Installing firefox..." sudo rm -r /opt/firefox -sudo mv firefox /opt/firefox58 +sudo mv firefox /opt/firefox61 sudo mv /usr/bin/firefox /usr/bin/firefox-old -sudo ln -s /opt/firefox58/firefox /usr/bin/firefox +sudo ln -s /opt/firefox61/firefox /usr/bin/firefox echo "Firefox installed." diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8517eed70..63d19c633 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,31 +2,26 @@ If you're submitting code to MetaMask, there are some simple things we'd appreciate you doing to help us stay organized! -## Submitting pull requests +### Finding the right project Before taking the time to code and implement something, feel free to open an issue and discuss it! There may even be an issue already open, and together we may come up with a specific strategy before you take your precious time to write code. -### Tests +There are also plenty of open issues we'd love help with. Search the [`good first issue`](https://github.com/MetaMask/metamask-extension/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) label, or head to Gitcoin and earn ETH for completing projects we've posted bounties on. -For any new programmatic functionality, we like unit tests when possible, so if you can keep your code cleanly isolated, please do add a test file to the `tests` folder. +If you're picking up a bounty or an existing issue, feel free to ask clarifying questions on the issue as you go about your work. -### PR Format +### Submitting a pull request +When you're done with your project / bugfix / feature and ready to submit a PR, there are a couple guidelines we ask you to follow: -If this PR closes the issue, add the line `Fixes #$ISSUE_NUMBER`. Ex. For closing issue 418, include the line `Fixes #418`. +- [ ] **Test it**: For any new programmatic functionality, we like unit tests when possible, so if you can keep your code cleanly isolated, please do add a test file to the `tests` folder. +- [ ] **Add to the CHANGELOG**: Help us keep track of all the moving pieces by adding an entry to the [`CHANGELOG.md`](https://github.com/MetaMask/metamask-extension/blob/develop/CHANGELOG.md) with a link to your PR. +- [ ] **Meet the spec**: Make sure the PR adds functionality that matches the issue you're closing. This is especially important for bounties: sometimes design or implementation details are included in the conversation, so read carefully! +- [ ] **Close the issue**: If this PR closes an open 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`. +- [ ] **Keep it simple**: Try not to include multiple features in a single PR, and don't make extraneous changes outside the scope of your contribution. All those touched files make things harder to review ;) +- [ ] **PR against `develop`**: 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. +- [ ] **Get reviewed by a core contributor**: Make sure you get a `:thumbsup`, `:+1`, or `LGTM` from a user with a `Member` badge before merging. -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. - -## Before Closing Issues - -Make sure the relevant code has been reviewed and merged. +And that's it! Thanks for helping out. ### Developing inside a node_modules folder diff --git a/app/404.html b/app/404.html new file mode 100644 index 000000000..8a6df9d7a --- /dev/null +++ b/app/404.html @@ -0,0 +1,52 @@ + + + MetaMask + + + +
+ +

Powered by Portal Network

+
+ + diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 9c3e43803..e1f321c68 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -691,6 +691,9 @@ "restoreVault": { "message": "Restore Vault" }, + "restoreAccountWithSeed": { + "message": "Restore your Account with Seed Phrase" + }, "required": { "message": "Required" }, @@ -700,6 +703,9 @@ "walletSeed": { "message": "Wallet Seed" }, + "restore": { + "message": "Restore" + }, "revealSeedWords": { "message": "Reveal Seed Words" }, @@ -804,6 +810,9 @@ "sendTokens": { "message": "Send Tokens" }, + "separateEachWord": { + "message": "Separate each word with a single space" + }, "onlySendToEtherAddress": { "message": "Only send ETH to an Ethereum address." }, diff --git a/app/error.html b/app/error.html new file mode 100644 index 000000000..366b3d94a --- /dev/null +++ b/app/error.html @@ -0,0 +1,79 @@ + + + MetaMask Error + + + + +
+ +

not found

+

Powered by Portal Network

+
+ + + diff --git a/app/images/404.png b/app/images/404.png new file mode 100644 index 000000000..b1a767dde Binary files /dev/null and b/app/images/404.png differ diff --git a/app/images/cancel.png b/app/images/cancel.png new file mode 100644 index 000000000..4e0eb1143 Binary files /dev/null and b/app/images/cancel.png differ diff --git a/app/images/deadface.png b/app/images/deadface.png new file mode 100644 index 000000000..e12476c03 Binary files /dev/null and b/app/images/deadface.png differ diff --git a/app/images/loginglogo.svg b/app/images/loginglogo.svg new file mode 100644 index 000000000..ca8b0a2ee --- /dev/null +++ b/app/images/loginglogo.svg @@ -0,0 +1,53 @@ + + + + logo2 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/images/logo.png b/app/images/logo.png new file mode 100644 index 000000000..db6ba7d6c Binary files /dev/null and b/app/images/logo.png differ diff --git a/app/images/pw-128x128.png b/app/images/pw-128x128.png new file mode 100644 index 000000000..a0eb1b730 Binary files /dev/null and b/app/images/pw-128x128.png differ diff --git a/app/images/pw-48x48.png b/app/images/pw-48x48.png new file mode 100644 index 000000000..a96c59e15 Binary files /dev/null and b/app/images/pw-48x48.png differ diff --git a/app/images/pw128x128.png b/app/images/pw128x128.png new file mode 100644 index 000000000..a0eb1b730 Binary files /dev/null and b/app/images/pw128x128.png differ diff --git a/app/loading.html b/app/loading.html new file mode 100644 index 000000000..aef5d9607 --- /dev/null +++ b/app/loading.html @@ -0,0 +1,35 @@ + + + MetaMask Loading + + + + + + diff --git a/app/manifest.json b/app/manifest.json index 50b7e3c53..a226adfb0 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -59,7 +59,10 @@ "unlimitedStorage", "clipboardWrite", "http://localhost:8545/", - "https://*.infura.io/" + "https://*.infura.io/", + "activeTab", + "webRequest", + "*://*.eth/" ], "web_accessible_resources": [ "inpage.js" @@ -72,4 +75,4 @@ "*" ] } -} \ No newline at end of file +} diff --git a/app/scripts/background.js b/app/scripts/background.js index 54511631f..1479d9f72 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -26,6 +26,8 @@ const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics') const EdgeEncryptor = require('./edge-encryptor') const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code') const getObjStructure = require('./lib/getObjStructure') +const ipfsContent = require('./lib/ipfsContent.js') + const { ENVIRONMENT_TYPE_POPUP, ENVIRONMENT_TYPE_NOTIFICATION, @@ -66,6 +68,7 @@ initialize().catch(log.error) // setup metamask mesh testing container setupMetamaskMeshMetrics() + /** * An object representing a transaction, in whatever state it is in. * @typedef TransactionMeta @@ -155,6 +158,7 @@ async function initialize () { const initLangCode = await getFirstPreferredLangCode() await setupController(initState, initLangCode) log.debug('MetaMask initialization complete.') + ipfsContent(initState.NetworkController.provider) } // @@ -258,6 +262,7 @@ function setupController (initState, initLangCode) { }) global.metamaskController = controller + // report failed transactions to Sentry controller.txController.on(`tx:status-update`, (txId, status) => { if (status !== 'failed') return diff --git a/app/scripts/lib/contracts/registrar.js b/app/scripts/lib/contracts/registrar.js new file mode 100644 index 000000000..99ca24458 --- /dev/null +++ b/app/scripts/lib/contracts/registrar.js @@ -0,0 +1 @@ +module.exports = [{'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'resolver', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'owner', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'label', 'type': 'bytes32'}, {'name': 'owner', 'type': 'address'}], 'name': 'setSubnodeOwner', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'ttl', 'type': 'uint64'}], 'name': 'setTTL', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'ttl', 'outputs': [{'name': '', 'type': 'uint64'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'resolver', 'type': 'address'}], 'name': 'setResolver', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'owner', 'type': 'address'}], 'name': 'setOwner', 'outputs': [], 'payable': false, 'type': 'function'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'owner', 'type': 'address'}], 'name': 'Transfer', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': true, 'name': 'label', 'type': 'bytes32'}, {'indexed': false, 'name': 'owner', 'type': 'address'}], 'name': 'NewOwner', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'resolver', 'type': 'address'}], 'name': 'NewResolver', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'ttl', 'type': 'uint64'}], 'name': 'NewTTL', 'type': 'event'}] diff --git a/app/scripts/lib/contracts/resolver.js b/app/scripts/lib/contracts/resolver.js new file mode 100644 index 000000000..1bf3f90ce --- /dev/null +++ b/app/scripts/lib/contracts/resolver.js @@ -0,0 +1,2 @@ +module.exports = +[{'constant': true, 'inputs': [{'name': 'interfaceID', 'type': 'bytes4'}], 'name': 'supportsInterface', 'outputs': [{'name': '', 'type': 'bool'}], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'contentTypes', 'type': 'uint256'}], 'name': 'ABI', 'outputs': [{'name': 'contentType', 'type': 'uint256'}, {'name': 'data', 'type': 'bytes'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'x', 'type': 'bytes32'}, {'name': 'y', 'type': 'bytes32'}], 'name': 'setPubkey', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'content', 'outputs': [{'name': 'ret', 'type': 'bytes32'}], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'addr', 'outputs': [{'name': 'ret', 'type': 'address'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'contentType', 'type': 'uint256'}, {'name': 'data', 'type': 'bytes'}], 'name': 'setABI', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'name', 'outputs': [{'name': 'ret', 'type': 'string'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'name', 'type': 'string'}], 'name': 'setName', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'hash', 'type': 'bytes32'}], 'name': 'setContent', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'pubkey', 'outputs': [{'name': 'x', 'type': 'bytes32'}, {'name': 'y', 'type': 'bytes32'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'addr', 'type': 'address'}], 'name': 'setAddr', 'outputs': [], 'payable': false, 'type': 'function'}, {'inputs': [{'name': 'ensAddr', 'type': 'address'}], 'payable': false, 'type': 'constructor'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'a', 'type': 'address'}], 'name': 'AddrChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'hash', 'type': 'bytes32'}], 'name': 'ContentChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'name', 'type': 'string'}], 'name': 'NameChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': true, 'name': 'contentType', 'type': 'uint256'}], 'name': 'ABIChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'x', 'type': 'bytes32'}, {'indexed': false, 'name': 'y', 'type': 'bytes32'}], 'name': 'PubkeyChanged', 'type': 'event'}] diff --git a/app/scripts/lib/ipfsContent.js b/app/scripts/lib/ipfsContent.js new file mode 100644 index 000000000..a6b99b2f9 --- /dev/null +++ b/app/scripts/lib/ipfsContent.js @@ -0,0 +1,40 @@ +const extension = require('extensionizer') +const resolver = require('./resolver.js') + +module.exports = function (provider) { + extension.webRequest.onBeforeRequest.addListener(details => { + const urlhttpreplace = details.url.replace(/\w+?:\/\//, '') + const url = urlhttpreplace.replace(/[\\/].*/g, '') // eslint-disable-line no-useless-escape + let domainhtml = urlhttpreplace.match(/[\\/].*/g) // eslint-disable-line no-useless-escape + let clearTime = null + const name = url.replace(/\/$/g, '') + if (domainhtml === null) domainhtml = [''] + extension.tabs.getSelected(null, tab => { + extension.tabs.update(tab.id, { url: 'loading.html' }) + + clearTime = setTimeout(() => { + return extension.tabs.update(tab.id, { url: '404.html' }) + }, 60000) + + resolver.resolve(name, provider).then(ipfsHash => { + clearTimeout(clearTime) + let url = 'https://ipfs.infura.io/ipfs/' + ipfsHash + domainhtml[0] + return fetch(url, { method: 'HEAD' }).then(response => response.status).then(statusCode => { + if (statusCode !== 200) return extension.tabs.update(tab.id, { url: '404.html' }) + extension.tabs.update(tab.id, { url: url }) + }) + .catch(err => { + url = 'https://ipfs.infura.io/ipfs/' + ipfsHash + domainhtml[0] + extension.tabs.update(tab.id, {url: url}) + return err + }) + }) + .catch(err => { + clearTimeout(clearTime) + const url = err === 'unsupport' ? 'unsupport' : 'error' + extension.tabs.update(tab.id, {url: `${url}.html?name=${name}`}) + }) + }) + return { cancel: true } + }, {urls: ['*://*.eth/', '*://*.eth/*']}) +} diff --git a/app/scripts/lib/resolver.js b/app/scripts/lib/resolver.js new file mode 100644 index 000000000..6786929d8 --- /dev/null +++ b/app/scripts/lib/resolver.js @@ -0,0 +1,71 @@ +const namehash = require('eth-ens-namehash') +const multihash = require('multihashes') +const HttpProvider = require('ethjs-provider-http') +const Eth = require('ethjs-query') +const EthContract = require('ethjs-contract') +const registrarAbi = require('./contracts/registrar') +const resolverAbi = require('./contracts/resolver') + +function ens (name, provider) { + const eth = new Eth(new HttpProvider(getProvider(provider.type))) + const hash = namehash.hash(name) + const contract = new EthContract(eth) + const Registrar = contract(registrarAbi).at(getRegistrar(provider.type)) + return new Promise((resolve, reject) => { + if (provider.type === 'mainnet' || provider.type === 'ropsten') { + Registrar.resolver(hash).then((address) => { + if (address === '0x0000000000000000000000000000000000000000') { + reject(null) + } else { + const Resolver = contract(resolverAbi).at(address['0']) + return Resolver.content(hash) + } + }).then((contentHash) => { + if (contentHash['0'] === '0x0000000000000000000000000000000000000000000000000000000000000000') reject(null) + if (contentHash.ret !== '0x') { + const hex = contentHash['0'].substring(2) + const buf = multihash.fromHexString(hex) + resolve(multihash.toB58String(multihash.encode(buf, 'sha2-256'))) + } else { + reject(null) + } + }) + } else { + return reject('unsupport') + } + }) +} + +function getProvider (type) { + switch (type) { + case 'mainnet': + return 'https://mainnet.infura.io/' + case 'ropsten': + return 'https://ropsten.infura.io/' + default: + return 'http://localhost:8545/' + } +} + +function getRegistrar (type) { + switch (type) { + case 'mainnet': + return '0x314159265dd8dbb310642f98f50c066173c1259b' + case 'ropsten': + return '0x112234455c3a32fd11230c42e7bccd4a84e02010' + default: + return '0x0000000000000000000000000000000000000000' + } +} + +module.exports.resolve = function (name, provider) { + const path = name.split('.') + const tld = path[path.length - 1] + if (tld === 'eth') { + return ens(name, provider) + } else { + return new Promise((resolve, reject) => { + reject(null) + }) + } +} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 1246629be..d70bac1c3 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -341,6 +341,7 @@ module.exports = class MetamaskController extends EventEmitter { markAccountsFound: this.markAccountsFound.bind(this), markPasswordForgotten: this.markPasswordForgotten.bind(this), unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this), + getGasPrice: (cb) => cb(null, this.getGasPrice()), // coinbase buyEth: this.buyEth.bind(this), diff --git a/app/unsupport.html b/app/unsupport.html new file mode 100644 index 000000000..6f514eb17 --- /dev/null +++ b/app/unsupport.html @@ -0,0 +1,59 @@ + + + + + MetaMask + + + +
+ +

ENS resolver only support on Ethereum mainnet

+
+ + \ No newline at end of file diff --git a/old-ui/app/app.js b/old-ui/app/app.js index 5c2dccc03..0637e3b5b 100644 --- a/old-ui/app/app.js +++ b/old-ui/app/app.js @@ -183,6 +183,7 @@ App.prototype.renderAppBar = function () { this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen }) }, }), + ]), props.isUnlocked && h('div', { diff --git a/old-ui/app/keychains/hd/restore-vault.js b/old-ui/app/keychains/hd/restore-vault.js index d334d8e5f..e9486a28d 100644 --- a/old-ui/app/keychains/hd/restore-vault.js +++ b/old-ui/app/keychains/hd/restore-vault.js @@ -42,9 +42,6 @@ RestoreVaultScreen.prototype.render = function () { // wallet seed entry h('h3', 'Wallet Seed'), h('textarea.twelve-word-phrase.letter-spacey', { - dataset: { - persistentFormId: 'wallet-seed', - }, placeholder: 'Enter your secret twelve word phrase here to restore your vault.', }), diff --git a/package-lock.json b/package-lock.json index e316705b4..a2b4fc34f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8352,11 +8352,11 @@ "from": "github:MetaMask/eth-contract-metadata#master" }, "eth-ens-namehash": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/eth-ens-namehash/-/eth-ens-namehash-1.0.2.tgz", - "integrity": "sha1-Bezda6wtf9e8XKhKmTxrrZ2k7bk=", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz", + "integrity": "sha1-IprEbsqG1S4MmR58sq74P/D2i88=", "requires": { - "idna-uts46": "^1.0.1", + "idna-uts46-hx": "^2.3.1", "js-sha3": "^0.5.7" }, "dependencies": { @@ -9301,6 +9301,22 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" }, + "eth-ens-namehash": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/eth-ens-namehash/-/eth-ens-namehash-1.0.2.tgz", + "integrity": "sha1-Bezda6wtf9e8XKhKmTxrrZ2k7bk=", + "requires": { + "idna-uts46": "^1.0.1", + "js-sha3": "^0.5.7" + }, + "dependencies": { + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" + } + } + }, "ethjs-contract": { "version": "0.1.9", "resolved": "https://registry.npmjs.org/ethjs-contract/-/ethjs-contract-0.1.9.tgz", @@ -15157,6 +15173,21 @@ "requires": { "punycode": "^2.1.0" }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + } + } + }, + "idna-uts46-hx": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz", + "integrity": "sha512-PWoF9Keq6laYdIRwwCdhTPl60xRqAloYNMQLiyUnG42VjT53oW07BXIRM+NK7eQjzXjAk2gUvX9caRxlnF9TAA==", + "requires": { + "punycode": "2.1.0" + }, "dependencies": { "punycode": { "version": "2.1.0", @@ -20105,6 +20136,38 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "multihashes": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-0.4.13.tgz", + "integrity": "sha512-HwJGEKPCpLlNlgGQA56CYh/Wsqa+c4JAq8+mheIgw7OK5T4QvNJqgp6TH8gZ4q4l1aiWeNat/H/MrFXmTuoFfQ==", + "requires": { + "bs58": "^4.0.1", + "varint": "^5.0.0" + }, + "dependencies": { + "base-x": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.4.tgz", + "integrity": "sha512-UYOadoSIkEI/VrRGSG6qp93rp2WdokiAiNYDfGW5qURAY8GiAQkvMbwNNSDYiVJopqv4gCna7xqf4rrNGp+5AA==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "requires": { + "base-x": "^3.0.2" + } + }, + "varint": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.0.tgz", + "integrity": "sha1-2Ca4n3SQcy+rwMDtaT7Uddyynr8=" + } + } + }, "multimatch": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", diff --git a/package.json b/package.json index c9253bb74..674ebd3a5 100644 --- a/package.json +++ b/package.json @@ -193,7 +193,9 @@ "web3": "^0.20.1", "web3-provider-engine": "^14.0.5", "web3-stream-provider": "^3.0.1", - "xtend": "^4.0.1" + "xtend": "^4.0.1", + "multihashes": "^0.4.12", + "eth-ens-namehash": "^2.0.8" }, "devDependencies": { "@sentry/cli": "^1.30.3", diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js index 5f270b52b..b07b1ecd7 100644 --- a/test/e2e/beta/metamask-beta-ui.spec.js +++ b/test/e2e/beta/metamask-beta-ui.spec.js @@ -355,9 +355,12 @@ describe('MetaMask', function () { 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() + const passwordInputs = await driver.findElements(By.css('input')) + await delay(regularDelayMs) + + passwordInputs[0].sendKeys('correct horse battery staple') + passwordInputs[1].sendKeys('correct horse battery staple') + await driver.findElement(By.css('.first-time-flow__button')).click() await delay(regularDelayMs) }) diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js index 241358135..d5e80151c 100644 --- a/test/integration/lib/send-new-ui.js +++ b/test/integration/lib/send-new-ui.js @@ -112,19 +112,8 @@ async function runSendFlowTest (assert, done) { errorMessage = $('.send-v2__error') assert.equal(errorMessage.length, 0, 'send should stop rendering amount error message after amount is corrected') - const sendGasField = await queryAsync($, '.send-v2__gas-fee-display') - assert.equal( - sendGasField.find('.currency-display__input-wrapper > input').val(), - '0.000021', - 'send gas field should show estimated gas total' - ) - assert.equal( - sendGasField.find('.currency-display__converted-value')[0].textContent, - '$0.03 USD', - 'send gas field should show estimated gas total converted to USD' - ) - await customizeGas(assert, 0, 21000, '0', '$0.00 USD') + await customizeGas(assert, 1, 21000, '0.000021', '$0.03 USD') await customizeGas(assert, 500, 60000, '0.03', '$36.03 USD') const sendButton = await queryAsync($, 'button.btn-primary.btn--large.page-container__footer-button') diff --git a/ui/app/actions.js b/ui/app/actions.js index c822ef3ee..f04de8fe8 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -6,7 +6,6 @@ const { calcGasTotal, calcTokenBalance, estimateGas, - estimateGasPriceFromRecentBlocks, } = require('./components/send_/send.utils') const ethUtil = require('ethereumjs-util') const { fetchLocale } = require('../i18n-helper') @@ -788,19 +787,26 @@ function updateGasData ({ }) { return (dispatch) => { dispatch(actions.gasLoadingStarted()) - const estimatedGasPrice = estimateGasPriceFromRecentBlocks(recentBlocks) - return Promise.all([ - Promise.resolve(estimatedGasPrice), - estimateGas({ - estimateGasMethod: background.estimateGas, - blockGasLimit, - selectedAddress, - selectedToken, - to, - value, - gasPrice: estimatedGasPrice, - }), - ]) + return new Promise((resolve, reject) => { + background.getGasPrice((err, data) => { + if (err) return reject(err) + return resolve(data) + }) + }) + .then(estimateGasPrice => { + return Promise.all([ + Promise.resolve(estimateGasPrice), + estimateGas({ + estimateGasMethod: background.estimateGas, + blockGasLimit, + selectedAddress, + selectedToken, + to, + value, + estimateGasPrice, + }), + ]) + }) .then(([gasPrice, gas]) => { dispatch(actions.setGasPrice(gasPrice)) dispatch(actions.setGasLimit(gas)) diff --git a/ui/app/app.js b/ui/app/app.js index d0e48a368..670b7e2d0 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -23,7 +23,7 @@ const Authenticated = require('./components/pages/authenticated') const Initialized = require('./components/pages/initialized') const Settings = require('./components/pages/settings') const UnlockPage = require('./components/pages/unlock-page') -const RestoreVaultPage = require('./components/pages/keychains/restore-vault') +const RestoreVaultPage = require('./components/pages/keychains/restore-vault').default const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed') const AddTokenPage = require('./components/pages/add-token') const ConfirmAddTokenPage = require('./components/pages/confirm-add-token') diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index cd8f76ed5..cefa428b9 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -31,8 +31,6 @@ const { } = require('../../conversion-util') const { - getGasPrice, - getGasLimit, getGasIsLoading, getForceGasMin, conversionRateSelector, @@ -44,6 +42,11 @@ const { getSendMaxModeState, } = require('../../selectors') +const { + getGasPrice, + getGasLimit, +} = require('../send_/send.selectors') + function mapStateToProps (state) { const selectedToken = getSelectedToken(state) const currentAccount = getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state) diff --git a/ui/app/components/pages/keychains/restore-vault.js b/ui/app/components/pages/keychains/restore-vault.js index 33575bfbb..d90a33e49 100644 --- a/ui/app/components/pages/keychains/restore-vault.js +++ b/ui/app/components/pages/keychains/restore-vault.js @@ -1,178 +1,189 @@ -const { withRouter } = require('react-router-dom') -const PropTypes = require('prop-types') -const { compose } = require('recompose') -const PersistentForm = require('../../../../lib/persistent-form') -const connect = require('../../../metamask-connect') -const h = require('react-hyperscript') -const { createNewVaultAndRestore, unMarkPasswordForgotten } = require('../../../actions') -const { DEFAULT_ROUTE } = require('../../../routes') -const log = require('loglevel') +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import {connect} from 'react-redux' +import { + createNewVaultAndRestore, + unMarkPasswordForgotten, +} from '../../../actions' +import { DEFAULT_ROUTE } from '../../../routes' +import TextField from '../../text-field' -class RestoreVaultPage extends PersistentForm { - constructor (props) { - super(props) - - this.state = { - error: null, - } +class RestoreVaultPage extends Component { + static contextTypes = { + t: PropTypes.func, } - createOnEnter (event) { - if (event.key === 'Enter') { - this.createNewVaultAndRestore() - } + static propTypes = { + warning: PropTypes.string, + createNewVaultAndRestore: PropTypes.func.isRequired, + leaveImportSeedScreenState: PropTypes.func, + history: PropTypes.object, + isLoading: PropTypes.bool, + }; + + state = { + seedPhrase: '', + password: '', + confirmPassword: '', + seedPhraseError: null, + passwordError: null, + confirmPasswordError: null, } - cancel () { - this.props.unMarkPasswordForgotten() - .then(this.props.history.push(DEFAULT_ROUTE)) + parseSeedPhrase = (seedPhrase) => { + return seedPhrase + .match(/\w+/g) + .join(' ') } - createNewVaultAndRestore () { - this.setState({ error: null }) + handleSeedPhraseChange (seedPhrase) { + let seedPhraseError = null - // check password - var passwordBox = document.getElementById('password-box') - var password = passwordBox.value - var passwordConfirmBox = document.getElementById('password-box-confirm') - var passwordConfirm = passwordConfirmBox.value - - if (password.length < 8) { - this.setState({ error: 'Password not long enough' }) - return + if (seedPhrase && this.parseSeedPhrase(seedPhrase).split(' ').length !== 12) { + seedPhraseError = this.context.t('seedPhraseReq') } - if (password !== passwordConfirm) { - this.setState({ error: 'Passwords don\'t match' }) - return + this.setState({ seedPhrase, seedPhraseError }) + } + + handlePasswordChange (password) { + const { confirmPassword } = this.state + let confirmPasswordError = null + let passwordError = null + + if (password && password.length < 8) { + passwordError = this.context.t('passwordNotLongEnough') } - // check seed - var seedBox = document.querySelector('textarea.twelve-word-phrase') - var seed = seedBox.value.trim() - if (seed.split(' ').length !== 12) { - this.setState({ error: 'Seed phrases are 12 words long' }) - return + if (confirmPassword && password !== confirmPassword) { + confirmPasswordError = this.context.t('passwordsDontMatch') } - // submit - this.props.createNewVaultAndRestore(password, seed) - .then(() => this.props.history.push(DEFAULT_ROUTE)) - .catch(({ message }) => { - this.setState({ error: message }) - log.error(message) - }) + 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 = () => { + const { password, seedPhrase } = this.state + const { + createNewVaultAndRestore, + leaveImportSeedScreenState, + history, + } = this.props + + leaveImportSeedScreenState() + createNewVaultAndRestore(password, this.parseSeedPhrase(seedPhrase)) + .then(() => history.push(DEFAULT_ROUTE)) + } + + hasError () { + const { passwordError, confirmPasswordError, seedPhraseError } = this.state + return passwordError || confirmPasswordError || seedPhraseError } render () { - const { error } = this.state - this.persistentFormParentId = 'restore-vault-form' + const { + seedPhrase, + password, + confirmPassword, + seedPhraseError, + passwordError, + confirmPasswordError, + } = this.state + const { t } = this.context + const { isLoading } = this.props + const disabled = !seedPhrase || !password || !confirmPassword || isLoading || this.hasError() return ( - h('.initialize-screen.flex-column.flex-center.flex-grow', [ - - h('h3.flex-center.text-transform-uppercase', { - style: { - background: '#EBEBEB', - color: '#AEAEAE', - marginBottom: 24, - width: '100%', - fontSize: '20px', - padding: 6, - }, - }, [ - this.props.t('restoreVault'), - ]), - - // wallet seed entry - h('h3', 'Wallet Seed'), - h('textarea.twelve-word-phrase.letter-spacey', { - dataset: { - persistentFormId: 'wallet-seed', - }, - placeholder: this.props.t('secretPhrase'), - }), - - // password - h('input.large-input.letter-spacey', { - type: 'password', - id: 'password-box', - placeholder: this.props.t('newPassword8Chars'), - dataset: { - persistentFormId: 'password', - }, - style: { - width: 260, - marginTop: 12, - }, - }), - - // confirm password - h('input.large-input.letter-spacey', { - type: 'password', - id: 'password-box-confirm', - placeholder: this.props.t('confirmPassword'), - onKeyPress: this.createOnEnter.bind(this), - dataset: { - persistentFormId: 'password-confirmation', - }, - style: { - width: 260, - marginTop: 16, - }, - }), - - error && ( - h('span.error.in-progress-notification', error) - ), - - // submit - h('.flex-row.flex-space-between', { - style: { - marginTop: 30, - width: '50%', - }, - }, [ - - // cancel - h('button.primary', { - onClick: () => this.cancel(), - }, this.props.t('cancel')), - - // submit - h('button.primary', { - onClick: this.createNewVaultAndRestore.bind(this), - }, this.props.t('ok')), - - ]), - ]) +
+
+
+ { + e.preventDefault() + this.props.history.goBack() + }} + href="#" + > + {`< Back`} + +
+ { this.context.t('restoreAccountWithSeed') } +
+
+ { this.context.t('secretPhrase') } +
+
+ +