diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f0151e6d..516e8f9e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,16 @@ ## Current Master +- Added a deprecation warning for web3 https://github.com/ethereum/mist/releases/tag/v0.9.0 + +## 3.9.6 2017-8-09 + - Replace account screen with an account drop-down menu. -- Replace confusing buttons with a new account-specific drop-down menu. +- Replace account buttons with a new account-specific drop-down menu. ## 3.9.5 2017-8-04 -- Improved phishing detection configuration update rate +- Improved phishing detection configuration update rate ## 3.9.4 2017-8-03 diff --git a/README.md b/README.md index e39058790..075db79c2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# MetaMask Plugin [![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/) +# MetaMask Plugin +[![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)](http://waffle.io/MetaMask/metamask-extension) ## Support diff --git a/app/manifest.json b/app/manifest.json index 3b9194eb9..f34bdcec3 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.9.5", + "version": "3.9.6", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", diff --git a/app/scripts/config.js b/app/scripts/config.js index 8e28db80e..c5f260583 100644 --- a/app/scripts/config.js +++ b/app/scripts/config.js @@ -12,4 +12,9 @@ module.exports = { kovan: KOVAN_RPC_URL, rinkeby: RINKEBY_RPC_URL, }, + networkNames: { + 3: 'Ropsten', + 4: 'Rinkeby', + 42: 'Kovan', + }, } diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js index 534047330..6abce73ea 100644 --- a/app/scripts/lib/auto-reload.js +++ b/app/scripts/lib/auto-reload.js @@ -5,7 +5,10 @@ function setupDappAutoReload (web3, observable) { global.web3 = new Proxy(web3, { get: (_web3, name) => { // get the time of use - if (name !== '_used') _web3._used = Date.now() + if (name !== '_used') { + console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider \nhttps://github.com/ethereum/mist/releases/tag/v0.9.0') + _web3._used = Date.now() + } return _web3[name] }, set: (_web3, name, value) => { diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index b64ea6712..5af078dc4 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -20,7 +20,15 @@ module.exports = class txProvideUtil { async analyzeGasUsage (txMeta) { const block = await this.query.getBlockByNumber('latest', true) - const estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit) + let estimatedGasHex + try { + estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit) + } catch (err) { + if (err.message.includes('Transaction execution error.')) { + txMeta.simulationFails = true + return txMeta + } + } this.setTxGas(txMeta, block.gasLimit, estimatedGasHex) return txMeta } @@ -35,8 +43,8 @@ module.exports = class txProvideUtil { const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20) txParams.gas = bnToHex(saferGasLimitBN) } - // run tx, see if it will OOG - return this.query.estimateGas(txParams) + // run tx + return await this.query.estimateGas(txParams) } setTxGas (txMeta, blockGasLimitHex, estimatedGasHex) { diff --git a/package.json b/package.json index d6b5752d0..8092e03c5 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", "ethjs-ens": "^2.0.0", - "ethjs-query": "^0.2.6", + "ethjs-query": "^0.2.9", "express": "^4.14.0", "extension-link-enabler": "^1.0.0", "extensionizer": "^1.0.0", diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js new file mode 100644 index 000000000..2be5d5c3a --- /dev/null +++ b/ui/app/account-detail.js @@ -0,0 +1,121 @@ +const inherits = require('util').inherits +const extend = require('xtend') +const Component = require('react').Component +const h = require('react-hyperscript') +const connect = require('react-redux').connect +const actions = require('./actions') +const valuesFor = require('./util').valuesFor +const Identicon = require('./components/identicon') +const EthBalance = require('./components/eth-balance') +const TransactionList = require('./components/transaction-list') +const ExportAccountView = require('./components/account-export') +const ethUtil = require('ethereumjs-util') +const EditableLabel = require('./components/editable-label') +const TabBar = require('./components/tab-bar') +const TokenList = require('./components/token-list') +const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns + +module.exports = connect(mapStateToProps)(AccountDetailScreen) + +function mapStateToProps (state) { + return { + metamask: state.metamask, + identities: state.metamask.identities, + accounts: state.metamask.accounts, + address: state.metamask.selectedAddress, + accountDetail: state.appState.accountDetail, + network: state.metamask.network, + unapprovedMsgs: valuesFor(state.metamask.unapprovedMsgs), + shapeShiftTxList: state.metamask.shapeShiftTxList, + transactions: state.metamask.selectedAddressTxList || [], + conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, + currentAccountTab: state.metamask.currentAccountTab, + tokens: state.metamask.tokens, + } +} + +inherits(AccountDetailScreen, Component) +function AccountDetailScreen () { + Component.call(this) +} + +// Note: This component is no longer used. Leaving the file for reference: +// - structuring routing for add token +// - state required for TxList +// Delete file when those features are complete +AccountDetailScreen.prototype.render = function () {} + +AccountDetailScreen.prototype.subview = function () { + var subview + try { + subview = this.props.accountDetail.subview + } catch (e) { + subview = null + } + + switch (subview) { + case 'transactions': + return this.tabSections() + case 'export': + var state = extend({key: 'export'}, this.props) + return h(ExportAccountView, state) + default: + return this.tabSections() + } +} + +AccountDetailScreen.prototype.tabSections = function () { + const { currentAccountTab } = this.props + + return h('section.tabSection.full-flex-height.grow-tenx', [ + + h(TabBar, { + tabs: [ + { content: 'Sent', key: 'history' }, + { content: 'Tokens', key: 'tokens' }, + ], + defaultTab: currentAccountTab || 'history', + tabSelected: (key) => { + this.props.dispatch(actions.setCurrentAccountTab(key)) + }, + }), + + this.tabSwitchView(), + ]) +} + +AccountDetailScreen.prototype.tabSwitchView = function () { + const props = this.props + const { address, network } = props + const { currentAccountTab, tokens } = this.props + + switch (currentAccountTab) { + case 'tokens': + return h(TokenList, { + userAddress: address, + network, + tokens, + addToken: () => this.props.dispatch(actions.showAddTokenPage()), + }) + default: + return this.transactionList() + } +} + +AccountDetailScreen.prototype.transactionList = function () { + const {transactions, unapprovedMsgs, address, + network, shapeShiftTxList, conversionRate } = this.props + + return h(TransactionList, { + transactions: transactions.sort((a, b) => b.time - a.time), + network, + unapprovedMsgs, + conversionRate, + address, + shapeShiftTxList, + viewPendingTx: (txId) => { + this.props.dispatch(actions.viewPendingTx(txId)) + }, + }) +} diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index 759eb15bd..15281171c 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -8,6 +8,7 @@ const ShapeshiftForm = require('./shapeshift-form') const Loading = require('./loading') const AccountPanel = require('./account-panel') const RadioList = require('./custom-radio-list') +const networkNames = require('../../../app/scripts/config.js').networkNames module.exports = connect(mapStateToProps)(BuyButtonSubview) @@ -30,16 +31,30 @@ function BuyButtonSubview () { } BuyButtonSubview.prototype.render = function () { + return ( + h('div', { + style: { + width: '100%', + }, + }, [ + this.headerSubview(), + this.primarySubview(), + ]) + ) +} + +BuyButtonSubview.prototype.headerSubview = function () { const props = this.props const isLoading = props.isSubLoading - return ( - h('.buy-eth-section.flex-column', { + + h('.flex-column', { style: { alignItems: 'center', }, }, [ - // back button + + // header bar (back button, label) h('.flex-row', { style: { alignItems: 'center', @@ -63,6 +78,8 @@ BuyButtonSubview.prototype.render = function () { }, }, 'Buy Eth'), ]), + + // loading indication h('div', { style: { position: 'absolute', @@ -70,8 +87,10 @@ BuyButtonSubview.prototype.render = function () { left: '49vw', }, }, [ - h(Loading, {isLoading}), + h(Loading, { isLoading }), ]), + + // account panel h('div', { style: { width: '80%', @@ -83,17 +102,92 @@ BuyButtonSubview.prototype.render = function () { account: props.account, }), ]), - h('h3.text-transform-uppercase', { + + h('.flex-row', { style: { - paddingLeft: '15px', - fontFamily: 'Montserrat Light', - width: '100vw', - background: 'rgb(235, 235, 235)', - color: 'rgb(174, 174, 174)', - paddingTop: '4px', - paddingBottom: '4px', + alignItems: 'center', + justifyContent: 'center', }, - }, 'Select Service'), + }, [ + h('h3.text-transform-uppercase.flex-center', { + style: { + paddingLeft: '15px', + width: '100vw', + background: 'rgb(235, 235, 235)', + color: 'rgb(174, 174, 174)', + paddingTop: '4px', + paddingBottom: '4px', + }, + }, 'Select Service'), + ]), + + ]) + + ) +} + + +BuyButtonSubview.prototype.primarySubview = function () { + const props = this.props + const network = props.network + + switch (network) { + case 'loading': + return + + case '1': + return this.mainnetSubview() + + // Ropsten, Rinkeby, Kovan + case '3': + case '4': + case '42': + const networkName = networkNames[network] + const label = `${networkName} Test Faucet` + return ( + h('div.flex-column', { + style: { + alignItems: 'center', + margin: '20px 50px', + }, + }, [ + h('button.text-transform-uppercase', { + onClick: () => this.props.dispatch(actions.buyEth({ network })), + style: { + marginTop: '15px', + }, + }, label), + // Kovan only: Dharma loans beta + network === '42' ? ( + h('button.text-transform-uppercase', { + onClick: () => this.navigateTo('https://borrow.dharma.io/'), + style: { + marginTop: '15px', + }, + }, 'Borrow With Dharma (Beta)') + ) : null, + ]) + ) + + default: + return ( + h('h2.error', 'Unknown network ID') + ) + + } +} + +BuyButtonSubview.prototype.mainnetSubview = function () { + const props = this.props + + return ( + + h('.flex-column', { + style: { + alignItems: 'center', + }, + }, [ + h('.flex-row.selected-exchange', { style: { position: 'relative', @@ -115,6 +209,7 @@ BuyButtonSubview.prototype.render = function () { onClick: this.radioHandler.bind(this), }), ]), + h('h3.text-transform-uppercase', { style: { paddingLeft: '15px', @@ -126,8 +221,10 @@ BuyButtonSubview.prototype.render = function () { paddingBottom: '4px', }, }, props.buyView.subview), + this.formVersionSubview(), ]) + ) } @@ -139,39 +236,6 @@ BuyButtonSubview.prototype.formVersionSubview = function () { } else if (this.props.buyView.formView.shapeshift) { return h(ShapeshiftForm, this.props) } - } else { - return h('div.flex-column', { - style: { - alignItems: 'center', - margin: '20px 50px', - }, - }, [ - h('h3.text-transform-uppercase', { - style: { - width: '225px', - marginBottom: '15px', - }, - }, 'In order to access this feature, please switch to the Main Network'), - ((network === '3') || (network === '4') || (network === '42')) ? h('h3.text-transform-uppercase', 'or go to the') : null, - (network === '3') ? h('button.text-transform-uppercase', { - onClick: () => this.props.dispatch(actions.buyEth({ network })), - style: { - marginTop: '15px', - }, - }, 'Ropsten Test Faucet') : null, - (network === '4') ? h('button.text-transform-uppercase', { - onClick: () => this.props.dispatch(actions.buyEth({ network })), - style: { - marginTop: '15px', - }, - }, 'Rinkeby Test Faucet') : null, - (network === '42') ? h('button.text-transform-uppercase', { - onClick: () => this.props.dispatch(actions.buyEth({ network })), - style: { - marginTop: '15px', - }, - }, 'Kovan Test Faucet') : null, - ]) } } diff --git a/ui/app/components/dropdowns/components/account-dropdowns.js b/ui/app/components/dropdowns/components/account-dropdowns.js index 11d109d73..b59f9bbf4 100644 --- a/ui/app/components/dropdowns/components/account-dropdowns.js +++ b/ui/app/components/dropdowns/components/account-dropdowns.js @@ -56,7 +56,16 @@ class AccountDropdowns extends Component { }, }, ), - h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, identity.name || ''), + h('span', { + style: { + marginLeft: '20px', + fontSize: '24px', + maxWidth: '145px', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }, + }, identity.name || ''), h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null), ] )