From 0e1e0aa32398b0b9d19cd6ae3fb06d577aae6cc6 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 14 Jun 2017 20:42:48 -0700 Subject: [PATCH 01/55] Create add token button and template view --- ui/app/account-detail.js | 6 ++- ui/app/actions.js | 11 +++- ui/app/add-token.js | 91 +++++++++++++++++++++++++++++++++ ui/app/app.js | 5 ++ ui/app/components/token-list.js | 63 ++++++++++++++++------- ui/app/reducers/app.js | 10 ++++ 6 files changed, 166 insertions(+), 20 deletions(-) create mode 100644 ui/app/add-token.js diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 836032b3c..19f2cba59 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -277,7 +277,11 @@ AccountDetailScreen.prototype.tabSwitchView = function () { switch (currentAccountTab) { case 'tokens': - return h(TokenList, { userAddress: address, network }) + return h(TokenList, { + userAddress: address, + network, + addToken: () => this.props.dispatch(actions.showAddTokenPage()), + }) default: return this.transactionList() } diff --git a/ui/app/actions.js b/ui/app/actions.js index b6b5d6eb1..d17d4610e 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -121,7 +121,9 @@ var actions = { SET_PROVIDER_TYPE: 'SET_PROVIDER_TYPE', USE_ETHERSCAN_PROVIDER: 'USE_ETHERSCAN_PROVIDER', useEtherscanProvider: useEtherscanProvider, - showConfigPage: showConfigPage, + showConfigPage, + SHOW_ADD_TOKEN_PAGE: 'SHOW_ADD_TOKEN_PAGE', + showAddTokenPage, setRpcTarget: setRpcTarget, setDefaultRpcTarget: setDefaultRpcTarget, setProviderType: setProviderType, @@ -627,6 +629,13 @@ function showConfigPage (transitionForward = true) { } } +function showAddTokenPage (transitionForward = true) { + return { + type: actions.SHOW_ADD_TOKEN_PAGE, + value: transitionForward, + } +} + function goBackToInitView () { return { type: actions.BACK_TO_INIT_MENU, diff --git a/ui/app/add-token.js b/ui/app/add-token.js new file mode 100644 index 000000000..5356b6a0b --- /dev/null +++ b/ui/app/add-token.js @@ -0,0 +1,91 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const connect = require('react-redux').connect +const actions = require('./actions') + +module.exports = connect(mapStateToProps)(AddTokenScreen) + +function mapStateToProps (state) { + return {} +} + +inherits(AddTokenScreen, Component) +function AddTokenScreen () { + this.state = { warning: null } + Component.call(this) +} + +AddTokenScreen.prototype.render = function () { + const state = this.state + const { warning } = state + return ( + h('.flex-column.flex-grow', [ + + // subtitle and nav + h('.section-title.flex-row.flex-center', [ + h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { + onClick: (event) => { + state.dispatch(actions.goHome()) + }, + }), + h('h2.page-subtitle', 'Add Token'), + ]), + + h('.error', { + style: { + display: warning ? 'block' : 'none', + padding: '0 20px', + textAlign: 'center', + }, + }, warning), + + // conf view + h('.flex-column.flex-justify-center.flex-grow.select-none', [ + h('.flex-space-around', { + style: { + padding: '20px', + }, + }, [ + + h('div', [ + h('span', { + style: { fontWeight: 'bold', paddingRight: '10px'}, + }, 'Token Sybmol'), + ]), + + h('div', { style: {display: 'flex'} }, [ + h('input#token_symbol', { + placeholder: `Like "ETH"`, + style: { + width: 'inherit', + flex: '1 0 auto', + height: '30px', + margin: '8px', + }, + onKeyPress (event) { + if (event.key === 'Enter') { + var element = event.target + var newRpc = element.value + } + }, + }), + ]), + + h('button', { + style: { + alignSelf: 'center', + }, + onClick (event) { + event.preventDefault() + var tokenSymbolEl = document.querySelector('input#token_symbol') + var tokenSymbol = tokenSymbolEl.value + console.log(tokenSymbol) + }, + }, 'Add'), + ]), + ]), + ]) + ) +} + diff --git a/ui/app/app.js b/ui/app/app.js index d444a8349..8bf69b5ad 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -19,6 +19,7 @@ const NoticeScreen = require('./components/notice') const generateLostAccountsNotice = require('../lib/lost-accounts-notice') // other views const ConfigScreen = require('./config') +const AddTokenScreen = require('./add-token') const Import = require('./accounts/import') const InfoScreen = require('./info') const Loading = require('./components/loading') @@ -458,6 +459,10 @@ App.prototype.renderPrimary = function () { log.debug('rendering confirm tx screen') return h(ConfirmTxScreen, {key: 'confirm-tx'}) + case 'add-token': + log.debug('rendering add-token screen from unlock screen.') + return h(AddTokenScreen, {key: 'add-token'}) + case 'config': log.debug('rendering config screen') return h(ConfigScreen, {key: 'config'}) diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js index c560a6072..d230ce74a 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -40,32 +40,59 @@ TokenList.prototype.render = function () { return h(TokenCell, tokenData) }) - return ( + return h('div', [ h('ol', { style: { - height: '302px', + height: '260px', overflowY: 'auto', + display: 'flex', + flexDirection: 'column', }, - }, [h('style', ` + }, [ + h('style', ` - li.token-cell { - display: flex; - flex-direction: row; - align-items: center; - padding: 10px; - } + li.token-cell { + display: flex; + flex-direction: row; + align-items: center; + padding: 10px; + } - li.token-cell > h3 { - margin-left: 12px; - } + li.token-cell > h3 { + margin-left: 12px; + } - li.token-cell:hover { - background: white; - cursor: pointer; - } + li.token-cell:hover { + background: white; + cursor: pointer; + } - `)].concat(tokenViews.length ? tokenViews : this.message('No Tokens Found.'))) - ) + `), + ...tokenViews, + tokenViews.length ? null : this.message('No Tokens Found.'), + ]), + this.addTokenButtonElement(), + ]) +} + +TokenList.prototype.addTokenButtonElement = function () { + return h('div', [ + h('div.footer.hover-white.pointer', { + key: 'reveal-account-bar', + onClick: () => { + this.props.addToken() + }, + style: { + display: 'flex', + height: '40px', + padding: '10px', + justifyContent: 'center', + alignItems: 'center', + }, + }, [ + h('i.fa.fa-plus.fa-lg'), + ]), + ]) } TokenList.prototype.message = function (body) { diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index deacad0a7..2fcc9bfe0 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -103,7 +103,17 @@ function reduceApp (state, action) { transForward: action.value, }) + case actions.SHOW_ADD_TOKEN_PAGE: + return extend(appState, { + currentView: { + name: 'add-token', + context: appState.currentView.context, + }, + transForward: action.value, + }) + case actions.SHOW_IMPORT_PAGE: + return extend(appState, { currentView: { name: 'import-menu', From f87c5f8a14fb892325735ed6377ed5b9464fe512 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 15 Jun 2017 13:31:37 -0700 Subject: [PATCH 02/55] Remove log --- ui/app/add-token.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/app/add-token.js b/ui/app/add-token.js index 5356b6a0b..cd47709ab 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -80,7 +80,6 @@ AddTokenScreen.prototype.render = function () { event.preventDefault() var tokenSymbolEl = document.querySelector('input#token_symbol') var tokenSymbol = tokenSymbolEl.value - console.log(tokenSymbol) }, }, 'Add'), ]), From 711a4def86d9e8c626ee554a976d9eab6abdcbee Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 15 Jun 2017 15:38:23 -0700 Subject: [PATCH 03/55] Make add token screen a fully working form Entering the address of a valid HumanStandardToken even auto-fills the other fields! --- ui/app/add-token.js | 160 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 145 insertions(+), 15 deletions(-) diff --git a/ui/app/add-token.js b/ui/app/add-token.js index cd47709ab..fdbb5fe53 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -4,21 +4,37 @@ const h = require('react-hyperscript') const connect = require('react-redux').connect const actions = require('./actions') +const ethUtil = require('ethereumjs-util') +const abi = require('human-standard-token-abi') +const Eth = require('ethjs-query') +const EthContract = require('ethjs-contract') + +const emptyAddr = '0x0000000000000000000000000000000000000000' + module.exports = connect(mapStateToProps)(AddTokenScreen) function mapStateToProps (state) { - return {} + return { + } } inherits(AddTokenScreen, Component) function AddTokenScreen () { - this.state = { warning: null } + this.state = { + warning: null, + address: null, + symbol: 'TOKEN', + decimals: 18, + } Component.call(this) } AddTokenScreen.prototype.render = function () { + const props = this.props + const state = this.state - const { warning } = state + const { warning, address, symbol, decimals } = state + return ( h('.flex-column.flex-grow', [ @@ -51,23 +67,69 @@ AddTokenScreen.prototype.render = function () { h('div', [ h('span', { style: { fontWeight: 'bold', paddingRight: '10px'}, - }, 'Token Sybmol'), + }, 'Token Address'), ]), - h('div', { style: {display: 'flex'} }, [ - h('input#token_symbol', { - placeholder: `Like "ETH"`, + h('section.flex-row.flex-center', [ + h('input#token-address', { + name: 'address', + placeholder: 'Token Address', + onChange: this.tokenAddressDidChange.bind(this), style: { width: 'inherit', flex: '1 0 auto', height: '30px', margin: '8px', }, - onKeyPress (event) { - if (event.key === 'Enter') { - var element = event.target - var newRpc = element.value - } + }), + ]), + + h('div', [ + h('span', { + style: { fontWeight: 'bold', paddingRight: '10px'}, + }, 'Token Sybmol'), + ]), + + h('div', { style: {display: 'flex'} }, [ + h('input#token_symbol', { + placeholder: `Like "ETH"`, + value: symbol, + style: { + width: 'inherit', + flex: '1 0 auto', + height: '30px', + margin: '8px', + }, + onChange: (event) => { + var element = event.target + var symbol = element.value + this.setState({ symbol }) + }, + }), + ]), + + h('div', [ + h('span', { + style: { fontWeight: 'bold', paddingRight: '10px'}, + }, 'Decimals of Precision'), + ]), + + h('div', { style: {display: 'flex'} }, [ + h('input#token_decimals', { + value: decimals, + type: 'number', + min: 0, + max: 36, + style: { + width: 'inherit', + flex: '1 0 auto', + height: '30px', + margin: '8px', + }, + onChange: (event) => { + var element = event.target + var decimals = element.value.trim() + this.setState({ decimals }) }, }), ]), @@ -77,9 +139,11 @@ AddTokenScreen.prototype.render = function () { alignSelf: 'center', }, onClick (event) { - event.preventDefault() - var tokenSymbolEl = document.querySelector('input#token_symbol') - var tokenSymbol = tokenSymbolEl.value + const valid = this.validateInputs() + if (!valid) return + + const { address, symbol, decimals } = state + this.props.dispatch(addToken(address.trim(), symbol.trim(), decimals)) }, }, 'Add'), ]), @@ -88,3 +152,69 @@ AddTokenScreen.prototype.render = function () { ) } +AddTokenScreen.prototype.componentWillMount = function () { + if (typeof global.ethereumProvider === 'undefined') return + + this.eth = new Eth(global.ethereumProvider) + this.contract = new EthContract(this.eth) + this.TokenContract = this.contract(abi) +} + +AddTokenScreen.prototype.tokenAddressDidChange = function (event) { + const el = event.target + const address = el.value.trim() + if (ethUtil.isValidAddress(address) && address !== emptyAddr) { + this.setState({ address }) + this.attemptToAutoFillTokenParams(address) + } +} + +AddTokenScreen.prototype.validateInputs = function () { + let msg = '' + const state = this.state + const { address, symbol, decimals } = state + + const validAddress = ethUtil.isValidAddress(address) + if (!validAddress) { + msg += 'Address is invalid. ' + } + + const validDecimals = decimals >= 0 && decimals < 36 + if (!validDecimals) { + msg += 'Decimals must be at least 0, and not over 36. ' + } + + const symbolLen = symbol.trim().length + const validSymbol = symbolLen > 0 && symbolLen < 10 + if (!validSymbol) { + msg += 'Symbol must be between 0 and 10 characters.' + } + + const isValid = validAddress && validDecimals + + if (!isValid) { + this.setState({ + warning: msg, + }) + } else { + this.setState({ warning: null }) + } + + return isValid +} + +AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) { + const contract = this.TokenContract.at(address) + + const results = await Promise.all([ + contract.symbol(), + contract.decimals(), + ]) + + const [ symbol, decimals ] = results + if (symbol && decimals) { + console.log('SETTING SYMBOL AND DECIMALS', { symbol, decimals }) + this.setState({ symbol: symbol[0], decimals: decimals[0].toString() }) + } +} + From 48789f2a3df2c820b61902fb49057f9f7b6cbd8c Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 15 Jun 2017 16:22:53 -0700 Subject: [PATCH 04/55] Add ability to add tokens to token list Fiex #1616 --- CHANGELOG.md | 1 + app/scripts/controllers/preferences.js | 29 ++++++++++++++++--- app/scripts/metamask-controller.js | 1 + ui/app/account-detail.js | 4 ++- ui/app/actions.js | 14 +++++++++ ui/app/add-token.js | 10 +++---- ui/app/components/token-list.js | 40 +++++++++++++++++++------- 7 files changed, 78 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37f334d23..567479862 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Current Master - Add list of popular tokens held to the account detail view. +- Add ability to add Tokens to token list. - Add a warning to JSON file import. ## 3.7.8 2017-6-12 diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index aa8e05fcc..e45224593 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -8,13 +8,11 @@ class PreferencesController { const initState = extend({ frequentRpcList: [], currentAccountTab: 'history', + tokens: [], }, opts.initState) this.store = new ObservableStore(initState) } - - // - // PUBLIC METHODS - // +// PUBLIC METHODS setSelectedAddress (_address) { return new Promise((resolve, reject) => { @@ -28,6 +26,29 @@ class PreferencesController { return this.store.getState().selectedAddress } + addToken (rawAddress, symbol, decimals) { + const address = normalizeAddress(rawAddress) + const newEntry = { address, symbol, decimals } + + const tokens = this.store.getState().tokens + const previousIndex = tokens.find((token, index) => { + return token.address === address + }) + + if (previousIndex) { + tokens[previousIndex] = newEntry + } else { + tokens.push(newEntry) + } + + this.store.updateState({ tokens }) + return Promise.resolve() + } + + getTokens () { + return this.store.getState().tokens + } + updateFrequentRpcList (_url) { return this.addToFrequentRpcList(_url) .then((rpcList) => { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 410693df4..e4267381d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -275,6 +275,7 @@ module.exports = class MetamaskController extends EventEmitter { // PreferencesController setSelectedAddress: nodeify(preferencesController.setSelectedAddress).bind(preferencesController), + addToken: nodeify(preferencesController.addToken).bind(preferencesController), setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab).bind(preferencesController), setDefaultRpc: nodeify(this.setDefaultRpc).bind(this), setCustomRpc: nodeify(this.setCustomRpc).bind(this), diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 19f2cba59..bed05a7fb 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -35,6 +35,7 @@ function mapStateToProps (state) { conversionRate: state.metamask.conversionRate, currentCurrency: state.metamask.currentCurrency, currentAccountTab: state.metamask.currentAccountTab, + tokens: state.metamask.tokens, } } @@ -273,13 +274,14 @@ AccountDetailScreen.prototype.tabSections = function () { AccountDetailScreen.prototype.tabSwitchView = function () { const props = this.props const { address, network } = props - const { currentAccountTab } = this.props + const { currentAccountTab, tokens } = this.props switch (currentAccountTab) { case 'tokens': return h(TokenList, { userAddress: address, network, + tokens, addToken: () => this.props.dispatch(actions.showAddTokenPage()), }) default: diff --git a/ui/app/actions.js b/ui/app/actions.js index d17d4610e..6ff28f32f 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -124,6 +124,7 @@ var actions = { showConfigPage, SHOW_ADD_TOKEN_PAGE: 'SHOW_ADD_TOKEN_PAGE', showAddTokenPage, + addToken, setRpcTarget: setRpcTarget, setDefaultRpcTarget: setDefaultRpcTarget, setProviderType: setProviderType, @@ -636,6 +637,19 @@ function showAddTokenPage (transitionForward = true) { } } +function addToken (address, symbol, decimals) { + return (dispatch) => { + dispatch(actions.showLoadingIndication()) + background.addToken(address, symbol, decimals, (err) => { + dispatch(actions.hideLoadingIndication()) + if (err) { + return dispatch(actions.displayWarning(err.message)) + } + dispatch(actions.goHome()) + }) + } +} + function goBackToInitView () { return { type: actions.BACK_TO_INIT_MENU, diff --git a/ui/app/add-token.js b/ui/app/add-token.js index fdbb5fe53..025cfacb5 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -30,10 +30,8 @@ function AddTokenScreen () { } AddTokenScreen.prototype.render = function () { - const props = this.props - const state = this.state - const { warning, address, symbol, decimals } = state + const { warning, symbol, decimals } = state return ( h('.flex-column.flex-grow', [ @@ -138,12 +136,12 @@ AddTokenScreen.prototype.render = function () { style: { alignSelf: 'center', }, - onClick (event) { + onClick: (event) => { const valid = this.validateInputs() if (!valid) return - const { address, symbol, decimals } = state - this.props.dispatch(addToken(address.trim(), symbol.trim(), decimals)) + const { address, symbol, decimals } = this.state + this.props.dispatch(actions.addToken(address.trim(), symbol.trim(), decimals)) }, }, 'Add'), ]), diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js index d230ce74a..100e596ed 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -4,13 +4,14 @@ const inherits = require('util').inherits const TokenTracker = require('eth-token-tracker') const TokenCell = require('./token-cell.js') const contracts = require('eth-contract-metadata') +const normalizeAddress = require('eth-sig-util').normalize -const tokens = [] +const defaultTokens = [] for (const address in contracts) { const contract = contracts[address] if (contract.erc20) { contract.address = address - tokens.push(contract) + defaultTokens.push(contract) } } @@ -18,22 +19,23 @@ module.exports = TokenList inherits(TokenList, Component) function TokenList () { - this.state = { tokens, isLoading: true, network: null } + this.state = { + tokens: null, + isLoading: true, + network: null, + } Component.call(this) } TokenList.prototype.render = function () { const state = this.state - const { tokens, isLoading } = state - - const { userAddress } = this.props + const { isLoading, tokens } = state + const { userAddress, network } = this.props if (isLoading) { return this.message('Loading') } - const network = this.props.network - const tokenViews = tokens.map((tokenData) => { tokenData.network = network tokenData.userAddress = userAddress @@ -120,7 +122,7 @@ TokenList.prototype.createFreshTokenTracker = function () { this.tracker = new TokenTracker({ userAddress, provider: global.ethereumProvider, - tokens: tokens, + tokens: uniqueMergeTokens(defaultTokens, this.props.tokens), pollingInterval: 8000, }) @@ -149,7 +151,12 @@ TokenList.prototype.componentWillUpdate = function (nextProps) { } TokenList.prototype.updateBalances = function (tokenData) { - const heldTokens = tokenData.filter(token => token.balance !== '0' && token.string !== '0.000') + const desired = this.props.tokens.map(token => token.address) + const heldTokens = tokenData.filter(token => { + const held = token.balance !== '0' && token.string !== '0.000' + const preferred = desired.includes(normalizeAddress(token.address)) + return held || preferred + }) this.setState({ tokens: heldTokens, isLoading: false }) } @@ -158,3 +165,16 @@ TokenList.prototype.componentWillUnmount = function () { this.tracker.stop() } +function uniqueMergeTokens (tokensA, tokensB) { + const uniqueAddresses = [] + const result = [] + tokensA.concat(tokensB).forEach((token) => { + const normal = normalizeAddress(token.address) + if (!uniqueAddresses.includes(normal)) { + uniqueAddresses.push(normal) + result.push(token) + } + }) + return result +} + From 235cb1f2d790a7bda349ab0d33ad1009751a8536 Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Mon, 19 Jun 2017 17:50:06 -0700 Subject: [PATCH 05/55] Keeps dapp gas price if set --- app/scripts/controllers/transactions.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index d9d9849b1..e3c2d74d3 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -152,13 +152,15 @@ module.exports = class TransactionController extends EventEmitter { const txParams = txMeta.txParams // ensure value txParams.value = txParams.value || '0x0' - this.query.gasPrice((err, gasPrice) => { - if (err) return cb(err) - // set gasPrice - txParams.gasPrice = gasPrice - // set gasLimit - this.txProviderUtils.analyzeGasUsage(txMeta, cb) - }) + if (!txParams.gasPrice) { + this.query.gasPrice((err, gasPrice) => { + if (err) return cb(err) + // set gasPrice + txParams.gasPrice = gasPrice + }) + } + // set gasLimit + this.txProviderUtils.analyzeGasUsage(txMeta, cb) } getUnapprovedTxList () { From 60855b05106899149824feecbd0f5d54907b0451 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 19 Jun 2017 16:12:34 -0700 Subject: [PATCH 06/55] Add send button to TokenFactory A simple solution to a temporary token send screen: Linking to EtherScan. Will hold us over until we make our own token send view. --- CHANGELOG.md | 1 + ui/app/components/token-cell.js | 36 ++++++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 567479862..6d22da332 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Add list of popular tokens held to the account detail view. - Add ability to add Tokens to token list. - Add a warning to JSON file import. +- Add "send" link to token list, which goes to TokenFactory. ## 3.7.8 2017-6-12 diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js index d3a895d36..1b226983b 100644 --- a/ui/app/components/token-cell.js +++ b/ui/app/components/token-cell.js @@ -17,12 +17,7 @@ TokenCell.prototype.render = function () { return ( h('li.token-cell', { style: { cursor: network === '1' ? 'pointer' : 'default' }, - onClick: (event) => { - const url = urlFor(address, userAddress, network) - if (url) { - navigateTo(url) - } - }, + onClick: this.view.bind(this, address, userAddress, network), }, [ h(Identicon, { @@ -32,15 +27,42 @@ TokenCell.prototype.render = function () { }), h('h3', `${string || 0} ${symbol}`), + + h('span', { style: { flex: '1 0 auto' } }), + + h('button', { + onClick: this.send.bind(this, address), + }, 'SEND'), + ]) ) } +TokenCell.prototype.send = function (address, event) { + event.preventDefault() + event.stopPropagation + const url = tokenFactoryFor(address) + if (url) { + navigateTo(url) + } +} + +TokenCell.prototype.view = function (address, userAddress, network, event) { + const url = etherscanLinkFor(address, userAddress, network) + if (url) { + navigateTo(url) + } +} + function navigateTo (url) { global.platform.openWindow({ url }) } -function urlFor (tokenAddress, address, network) { +function etherscanLinkFor (tokenAddress, address, network) { return `https://etherscan.io/token/${tokenAddress}?a=${address}` } +function tokenFactoryFor (tokenAddress) { + return `https://tokenfactory.surge.sh/#/token/${tokenAddress}` +} + From 97ab48ba0d5c0699b4ec0ad4bbc3d9c8805ef048 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 20 Jun 2017 08:01:00 -0700 Subject: [PATCH 07/55] Fix propagation --- ui/app/components/token-cell.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js index 1b226983b..67558ad87 100644 --- a/ui/app/components/token-cell.js +++ b/ui/app/components/token-cell.js @@ -40,7 +40,7 @@ TokenCell.prototype.render = function () { TokenCell.prototype.send = function (address, event) { event.preventDefault() - event.stopPropagation + event.stopPropagation() const url = tokenFactoryFor(address) if (url) { navigateTo(url) From 027394b2058b31daa399c582c82f0c0b01571144 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 20 Jun 2017 08:58:25 -0700 Subject: [PATCH 08/55] Reduce token list clutter by only showing held tokens We could change this when we allow hiding/removing tokens, but for now, this is a simple and pleasant solution. --- ui/app/components/token-list.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js index 100e596ed..bc41c5270 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -20,7 +20,7 @@ module.exports = TokenList inherits(TokenList, Component) function TokenList () { this.state = { - tokens: null, + tokens: [], isLoading: true, network: null, } @@ -150,12 +150,9 @@ TokenList.prototype.componentWillUpdate = function (nextProps) { } } -TokenList.prototype.updateBalances = function (tokenData) { - const desired = this.props.tokens.map(token => token.address) - const heldTokens = tokenData.filter(token => { - const held = token.balance !== '0' && token.string !== '0.000' - const preferred = desired.includes(normalizeAddress(token.address)) - return held || preferred +TokenList.prototype.updateBalances = function (tokens) { + const heldTokens = tokens.filter(token => { + return token.balance !== '0' && token.string !== '0.000' }) this.setState({ tokens: heldTokens, isLoading: false }) } From 7628a83fd67fcc0f0dca798ea6c5d34eeefb744a Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Mon, 26 Jun 2017 11:47:08 -0700 Subject: [PATCH 09/55] Fix Back Button on Add Token View --- ui/app/add-token.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/app/add-token.js b/ui/app/add-token.js index 025cfacb5..b303b5c0d 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -31,6 +31,7 @@ function AddTokenScreen () { AddTokenScreen.prototype.render = function () { const state = this.state + const props = this.props const { warning, symbol, decimals } = state return ( @@ -40,7 +41,7 @@ AddTokenScreen.prototype.render = function () { h('.section-title.flex-row.flex-center', [ h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { onClick: (event) => { - state.dispatch(actions.goHome()) + props.dispatch(actions.goHome()) }, }), h('h2.page-subtitle', 'Add Token'), From 31da623c21463e93737cfcb36cbf10b2d30f7e2a Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 26 Jun 2017 14:01:09 -0700 Subject: [PATCH 10/55] Fix react warning on create-vault-complete --- ui/app/keychains/hd/create-vault-complete.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/keychains/hd/create-vault-complete.js b/ui/app/keychains/hd/create-vault-complete.js index 9741155f7..a318a9b50 100644 --- a/ui/app/keychains/hd/create-vault-complete.js +++ b/ui/app/keychains/hd/create-vault-complete.js @@ -20,7 +20,7 @@ function mapStateToProps (state) { CreateVaultCompleteScreen.prototype.render = function () { var state = this.props - var seed = state.seed || state.cachedSeed + var seed = state.seed || state.cachedSeed || '' return ( From f925a37a9f79337951a0ffd8a106929b3f75d22b Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 26 Jun 2017 14:01:35 -0700 Subject: [PATCH 11/55] Fix react warning for keys on ens address book --- ui/app/components/ens-input.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index 16c50db84..3a33ebf74 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -41,7 +41,6 @@ EnsInput.prototype.render = function () { this.checkName() }, }) - return h('div', { style: { width: '100%' }, }, [ @@ -55,6 +54,7 @@ EnsInput.prototype.render = function () { return h('option', { value: identity.address, label: identity.name, + key: identity.address, }) }), // Corresponds to previously sent-to addresses. From ca832959c224a184c0ad40f5dd4239ec261b7f6b Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Tue, 27 Jun 2017 10:34:02 -0700 Subject: [PATCH 12/55] Update change log --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96dc79d9a..a6b988b4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Seed word confirmation wording is now scarier. - Fix error for invalid seed words. - Prevent users from submitting two duplicate transactions by disabling submit. +- Allow Dapps to specify gas price as hex string. ## 3.7.8 2017-6-12 From a3526906613c9047e6a2bd6bd7c11934152a32fb Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Tue, 27 Jun 2017 12:09:50 -0700 Subject: [PATCH 13/55] Remove trailing periods and white space --- ui/app/send.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/send.js b/ui/app/send.js index fd6994145..34e0ea70a 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -244,7 +244,7 @@ SendTransactionScreen.prototype.recipientDidChange = function (recipient, nickna SendTransactionScreen.prototype.onSubmit = function () { const state = this.state || {} - const recipient = state.recipient || document.querySelector('input[name="address"]').value + const recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '') const nickname = state.nickname || ' ' const input = document.querySelector('input[name="amount"]').value const value = util.normalizeEthStringToWei(input) From 77054e1fbb588f81403b21999217fccb4d140bf0 Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Tue, 27 Jun 2017 13:56:51 -0700 Subject: [PATCH 14/55] Rename Send to Next --- ui/app/send.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/send.js b/ui/app/send.js index fd6994145..70d26d50a 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -189,7 +189,7 @@ SendTransactionScreen.prototype.render = function () { style: { textTransform: 'uppercase', }, - }, 'Send'), + }, 'Next'), ]), From db2836a1ae5bfb2e641ab2b68a9853297e97b64b Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 27 Jun 2017 14:19:28 -0700 Subject: [PATCH 15/55] dont stop retrying brodcasting txs --- CHANGELOG.md | 1 + app/scripts/controllers/transactions.js | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96dc79d9a..872db1c1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Current Master +- No longer stop rebroadcasting transactions - Add list of popular tokens held to the account detail view. - Add a warning to JSON file import. - Fix bug where slowly mined txs would sometimes be incorrectly marked as failed. diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index f6dea34e7..31cf8239a 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -8,8 +8,6 @@ const TxProviderUtil = require('../lib/tx-utils') const createId = require('../lib/random-id') const denodeify = require('denodeify') -const RETRY_LIMIT = 200 - module.exports = class TransactionController extends EventEmitter { constructor (opts) { super() @@ -435,8 +433,6 @@ module.exports = class TransactionController extends EventEmitter { // Only auto-submit already-signed txs: if (!('rawTx' in txMeta)) return cb() - if (txMeta.retryCount > RETRY_LIMIT) return - // Increment a try counter. txMeta.retryCount++ const rawTx = txMeta.rawTx From 5cfce8c45a10db8271682b282d4c41747e01943a Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 27 Jun 2017 14:45:43 -0700 Subject: [PATCH 16/55] Fix token adding bug --- ui/app/actions.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index 6ff28f32f..d99291e46 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -645,7 +645,9 @@ function addToken (address, symbol, decimals) { if (err) { return dispatch(actions.displayWarning(err.message)) } - dispatch(actions.goHome()) + setTimeout(() => { + dispatch(actions.goHome()) + }, 250) }) } } From 3aff9fdd2a708768e8f4d82ad8756c62a5f3e55c Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 27 Jun 2017 14:49:41 -0700 Subject: [PATCH 17/55] Support other network links --- ui/app/components/token-cell.js | 4 +++- ui/lib/etherscan-prefix-for-network.js | 21 +++++++++++++++++++++ ui/lib/explorer-link.js | 21 +++------------------ 3 files changed, 27 insertions(+), 19 deletions(-) create mode 100644 ui/lib/etherscan-prefix-for-network.js diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js index d3a895d36..4d2cacb01 100644 --- a/ui/app/components/token-cell.js +++ b/ui/app/components/token-cell.js @@ -2,6 +2,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits const Identicon = require('./identicon') +const prefixForNetwork = require('../../lib/etherscan-prefix-for-network') module.exports = TokenCell @@ -41,6 +42,7 @@ function navigateTo (url) { } function urlFor (tokenAddress, address, network) { - return `https://etherscan.io/token/${tokenAddress}?a=${address}` + const prefix = prefixForNetwork(network) + return `https://${prefix}etherscan.io/token/${tokenAddress}?a=${address}` } diff --git a/ui/lib/etherscan-prefix-for-network.js b/ui/lib/etherscan-prefix-for-network.js new file mode 100644 index 000000000..eec658d8f --- /dev/null +++ b/ui/lib/etherscan-prefix-for-network.js @@ -0,0 +1,21 @@ +module.exports = function (hash, network) { + const net = parseInt(network) + let prefix + switch (net) { + case 1: // main net + prefix = '' + break + case 3: // ropsten test net + prefix = 'ropsten.' + break + case 4: // rinkeby test net + prefix = 'rinkeby.' + break + case 42: // kovan test net + prefix = 'kovan.' + break + default: + prefix = '' + } + return prefix +} diff --git a/ui/lib/explorer-link.js b/ui/lib/explorer-link.js index e11249551..3b82ecd5f 100644 --- a/ui/lib/explorer-link.js +++ b/ui/lib/explorer-link.js @@ -1,21 +1,6 @@ +const prefixForNetwork = require('./etherscan-prefix-for-network') + module.exports = function (hash, network) { - const net = parseInt(network) - let prefix - switch (net) { - case 1: // main net - prefix = '' - break - case 3: // ropsten test net - prefix = 'ropsten.' - break - case 4: // rinkeby test net - prefix = 'rinkeby.' - break - case 42: // kovan test net - prefix = 'kovan.' - break - default: - prefix = '' - } + const prefix = prefixForNetwork(network) return `http://${prefix}etherscan.io/tx/${hash}` } From 5440ed23d621c9ebcd24f89d54701f978e1c086e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 27 Jun 2017 14:49:41 -0700 Subject: [PATCH 18/55] Support other network links --- ui/app/components/token-cell.js | 4 +++- ui/lib/etherscan-prefix-for-network.js | 21 +++++++++++++++++++++ ui/lib/explorer-link.js | 21 +++------------------ 3 files changed, 27 insertions(+), 19 deletions(-) create mode 100644 ui/lib/etherscan-prefix-for-network.js diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js index d3a895d36..4d2cacb01 100644 --- a/ui/app/components/token-cell.js +++ b/ui/app/components/token-cell.js @@ -2,6 +2,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits const Identicon = require('./identicon') +const prefixForNetwork = require('../../lib/etherscan-prefix-for-network') module.exports = TokenCell @@ -41,6 +42,7 @@ function navigateTo (url) { } function urlFor (tokenAddress, address, network) { - return `https://etherscan.io/token/${tokenAddress}?a=${address}` + const prefix = prefixForNetwork(network) + return `https://${prefix}etherscan.io/token/${tokenAddress}?a=${address}` } diff --git a/ui/lib/etherscan-prefix-for-network.js b/ui/lib/etherscan-prefix-for-network.js new file mode 100644 index 000000000..2c1904f1c --- /dev/null +++ b/ui/lib/etherscan-prefix-for-network.js @@ -0,0 +1,21 @@ +module.exports = function (network) { + const net = parseInt(network) + let prefix + switch (net) { + case 1: // main net + prefix = '' + break + case 3: // ropsten test net + prefix = 'ropsten.' + break + case 4: // rinkeby test net + prefix = 'rinkeby.' + break + case 42: // kovan test net + prefix = 'kovan.' + break + default: + prefix = '' + } + return prefix +} diff --git a/ui/lib/explorer-link.js b/ui/lib/explorer-link.js index e11249551..3b82ecd5f 100644 --- a/ui/lib/explorer-link.js +++ b/ui/lib/explorer-link.js @@ -1,21 +1,6 @@ +const prefixForNetwork = require('./etherscan-prefix-for-network') + module.exports = function (hash, network) { - const net = parseInt(network) - let prefix - switch (net) { - case 1: // main net - prefix = '' - break - case 3: // ropsten test net - prefix = 'ropsten.' - break - case 4: // rinkeby test net - prefix = 'rinkeby.' - break - case 42: // kovan test net - prefix = 'kovan.' - break - default: - prefix = '' - } + const prefix = prefixForNetwork(network) return `http://${prefix}etherscan.io/tx/${hash}` } From 78af771c797a3a9d57970ef94be3c3e5ecf117c6 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 27 Jun 2017 15:02:15 -0700 Subject: [PATCH 19/55] Do not allow adding non token addresses --- ui/app/add-token.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ui/app/add-token.js b/ui/app/add-token.js index b303b5c0d..f21184270 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -142,7 +142,13 @@ AddTokenScreen.prototype.render = function () { if (!valid) return const { address, symbol, decimals } = this.state - this.props.dispatch(actions.addToken(address.trim(), symbol.trim(), decimals)) + this.checkIfToken(address.trim()) + .then(() => { + this.props.dispatch(actions.addToken(address.trim(), symbol.trim(), decimals)) + }) + .catch((reason) => { + this.setState({ warning: 'Not a valid token address.' }) + }) }, }, 'Add'), ]), @@ -202,6 +208,12 @@ AddTokenScreen.prototype.validateInputs = function () { return isValid } +AddTokenScreen.prototype.checkIfToken = async function (address) { + const contract = this.TokenContract.at(address) + const result = await contract.balance(address) + return result[0].toString() +} + AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) { const contract = this.TokenContract.at(address) From 4e0ec74bb7cb36e2e0fa035bf653ce0e57b7c2e7 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 27 Jun 2017 14:59:37 -0700 Subject: [PATCH 20/55] Create a migration for setting tx's with the message 'Gave up submitting tx.' as failed --- app/scripts/migrations/015.js | 38 +++++++++++++++++++++++++++++++++ app/scripts/migrations/index.js | 1 + 2 files changed, 39 insertions(+) create mode 100644 app/scripts/migrations/015.js diff --git a/app/scripts/migrations/015.js b/app/scripts/migrations/015.js new file mode 100644 index 000000000..4b839580b --- /dev/null +++ b/app/scripts/migrations/015.js @@ -0,0 +1,38 @@ +const version = 15 + +/* + +This migration sets transactions with the 'Gave up submitting tx.' err message +to a 'failed' stated + +*/ + +const clone = require('clone') + +module.exports = { + version, + + migrate: function (originalVersionedData) { + const versionedData = clone(originalVersionedData) + versionedData.meta.version = version + try { + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + } catch (err) { + console.warn(`MetaMask Migration #${version}` + err.stack) + } + return Promise.resolve(versionedData) + }, +} + +function transformState (state) { + const newState = state + const transactions = newState.TransactionController.transactions + newState.TransactionController.transactions = transactions.map((txMeta) => { + if (!txMeta.err) return txMeta + else if (txMeta.err.message === 'Gave up submitting tx.') txMeta.status = 'failed' + return txMeta + }) + return newState +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index fb1ad7863..651ee6a9c 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -25,4 +25,5 @@ module.exports = [ require('./012'), require('./013'), require('./014'), + require('./015'), ] From 8642feee09f57d5756c682a053434a196cff4af3 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 27 Jun 2017 15:49:57 -0700 Subject: [PATCH 21/55] Remove token contract validation step --- ui/app/add-token.js | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/ui/app/add-token.js b/ui/app/add-token.js index f21184270..b303b5c0d 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -142,13 +142,7 @@ AddTokenScreen.prototype.render = function () { if (!valid) return const { address, symbol, decimals } = this.state - this.checkIfToken(address.trim()) - .then(() => { - this.props.dispatch(actions.addToken(address.trim(), symbol.trim(), decimals)) - }) - .catch((reason) => { - this.setState({ warning: 'Not a valid token address.' }) - }) + this.props.dispatch(actions.addToken(address.trim(), symbol.trim(), decimals)) }, }, 'Add'), ]), @@ -208,12 +202,6 @@ AddTokenScreen.prototype.validateInputs = function () { return isValid } -AddTokenScreen.prototype.checkIfToken = async function (address) { - const contract = this.TokenContract.at(address) - const result = await contract.balance(address) - return result[0].toString() -} - AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) { const contract = this.TokenContract.at(address) From d5f0ee4f5e6a0c608ea200fb8c6593641505296d Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Tue, 27 Jun 2017 18:40:47 -0700 Subject: [PATCH 22/55] Add Back Button for Import Screen --- ui/app/accounts/import/index.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ui/app/accounts/import/index.js b/ui/app/accounts/import/index.js index a0f0f9bdb..97b387229 100644 --- a/ui/app/accounts/import/index.js +++ b/ui/app/accounts/import/index.js @@ -2,6 +2,7 @@ const inherits = require('util').inherits const Component = require('react').Component const h = require('react-hyperscript') const connect = require('react-redux').connect +const actions = require('../../actions') import Select from 'react-select' // Subviews @@ -37,6 +38,14 @@ AccountImportSubview.prototype.render = function () { style: { }, }, [ + h('.section-title.flex-row.flex-center', [ + h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { + onClick: (event) => { + props.dispatch(actions.goHome()) + }, + }), + h('h2.page-subtitle', 'Import Accounts'), + ]), h('div', { style: { padding: '10px', From 1b8a4395ab2a7b84b37676ccf08ba58f4d12fccc Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 28 Jun 2017 10:22:12 -0700 Subject: [PATCH 23/55] Add copy state logs button --- CHANGELOG.md | 1 + ui/app/config.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d8a814b9..19bb14e85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Fix error for invalid seed words. - Prevent users from submitting two duplicate transactions by disabling submit. - Allow Dapps to specify gas price as hex string. +- Add button for copying state logs to clipboard. ## 3.7.8 2017-6-12 diff --git a/ui/app/config.js b/ui/app/config.js index d7be26757..62785c49b 100644 --- a/ui/app/config.js +++ b/ui/app/config.js @@ -5,6 +5,7 @@ const connect = require('react-redux').connect const actions = require('./actions') const currencies = require('./conversion.json').rows const validUrl = require('valid-url') +const copyToClipboard = require('copy-to-clipboard') module.exports = connect(mapStateToProps)(ConfigScreen) @@ -85,8 +86,35 @@ ConfigScreen.prototype.render = function () { }, }, 'Save'), ]), + h('hr.horizontal-line'), + currentConversionInformation(metamaskState, state), + + h('hr.horizontal-line'), + + h('div', { + style: { + marginTop: '20px', + }, + }, [ + h('p', { + style: { + fontFamily: 'Montserrat Light', + fontSize: '13px', + }, + }, `State logs contain your public account addresses and sent transactions.`), + h('br'), + h('button', { + style: { + alignSelf: 'center', + }, + onClick (event) { + copyToClipboard(window.logState()) + }, + }, 'Copy State Logs'), + ]), + h('hr.horizontal-line'), h('div', { From d7bcd9458f994bc7599c97623bf73a0c3367d7cf Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 28 Jun 2017 10:41:58 -0700 Subject: [PATCH 24/55] Version 3.8.0 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0018fc76..e88d085e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 3.8.0 2017-6-28 + - No longer stop rebroadcasting transactions - Add list of popular tokens held to the account detail view. - Add ability to add Tokens to token list. diff --git a/app/manifest.json b/app/manifest.json index 7ae20158c..1cd909732 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.7.8", + "version": "3.8.0", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From c81a3c649a53fd09fedc9c8d0a8cab49347186d6 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 28 Jun 2017 16:36:58 -0700 Subject: [PATCH 25/55] Add padding to token messages --- ui/app/components/token-list.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js index ac7ab8309..bf352dc11 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -109,6 +109,7 @@ TokenList.prototype.message = function (body) { height: '250px', alignItems: 'center', justifyContent: 'center', + padding: '30px', }, }, body) } From 5e8b4e3226a4b084c418c7ed709d4e0f34ab24ec Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 29 Jun 2017 12:06:22 -0700 Subject: [PATCH 26/55] =?UTF-8?q?change=20=E2=80=9CACCEPT=E2=80=9D=20to=20?= =?UTF-8?q?=E2=80=9CSUBMIT=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/app/components/pending-tx.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index f33a5d948..d7d602f31 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -315,7 +315,7 @@ PendingTx.prototype.render = function () { // Accept Button h('input.confirm.btn-green', { type: 'submit', - value: 'ACCEPT', + value: 'SUBMIT', style: { marginLeft: '10px' }, disabled: insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting, }), From f285fd5eb1b8706da7bdb694543b96b7bed819f1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 29 Jun 2017 14:56:24 -0700 Subject: [PATCH 27/55] Bump web3 version to 0.9.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3f62923f8..6ab265ec2 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "through2": "^2.0.1", "valid-url": "^1.0.9", "vreme": "^3.0.2", - "web3": "0.18.2", + "web3": "0.19.1", "web3-provider-engine": "^13.0.3", "web3-stream-provider": "^2.0.6", "xtend": "^4.0.1" From 63acc0f4c8606f5f0e18dc716cecd4dbfc20a4b4 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 29 Jun 2017 18:50:21 -0700 Subject: [PATCH 28/55] deps - remove duplicated dev-dependencies ``` npm WARN The package clone is included as both a dev and production dependency. npm WARN The package react-dom is included as both a dev and production dependency. ``` --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 6ab265ec2..a8aec2f04 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "qrcode-npm": "0.0.3", "react": "^15.0.2", "react-addons-css-transition-group": "^15.0.2", - "react-dom": "^15.0.2", + "react-dom": "^15.5.4", "react-hyperscript": "^2.2.2", "react-markdown": "^2.3.0", "react-redux": "^4.4.5", @@ -141,7 +141,6 @@ "brfs": "^1.4.3", "browserify": "^13.0.0", "chai": "^3.5.0", - "clone": "^1.0.2", "deep-freeze-strict": "^1.1.1", "del": "^2.2.0", "envify": "^4.0.0", @@ -173,7 +172,6 @@ "qs": "^6.2.0", "qunit": "^0.9.1", "react-addons-test-utils": "^15.5.1", - "react-dom": "^15.5.4", "react-test-renderer": "^15.5.4", "react-testutils-additions": "^15.2.0", "sinon": "^1.17.3", From c7f2fd279d40d401260991a76787459761a453e4 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 30 Jun 2017 09:45:34 -0700 Subject: [PATCH 29/55] Bump token-tracker to 1.1.1 Includes a critical decimal-handling fix. Also reduces number of symbol and precision queries after initial load. --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e88d085e0..808fe6939 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Current Master +- Fix precision bug in token balances. +- Cache token symbol and precisions to reduce network load. + ## 3.8.0 2017-6-28 - No longer stop rebroadcasting transactions diff --git a/package.json b/package.json index a8aec2f04..77d3531ff 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "eth-query": "^2.1.2", "eth-sig-util": "^1.1.1", "eth-simple-keyring": "^1.1.1", - "eth-token-tracker": "^1.0.9", + "eth-token-tracker": "^1.1.1", "ethereumjs-tx": "^1.3.0", "ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", From 8179f5f84c288d47847540c8f189e6ee8170e701 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 30 Jun 2017 10:10:53 -0700 Subject: [PATCH 30/55] Bump token-tracker to 1.1.2 To restore older firefox compatibility. Fixes #1696 --- CHANGELOG.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 808fe6939..143e7ca9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Fix precision bug in token balances. - Cache token symbol and precisions to reduce network load. +- Transpile some newer JavaScript, restores compatibility with some older browsers. ## 3.8.0 2017-6-28 diff --git a/package.json b/package.json index 77d3531ff..3b608af0e 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "eth-query": "^2.1.2", "eth-sig-util": "^1.1.1", "eth-simple-keyring": "^1.1.1", - "eth-token-tracker": "^1.1.1", + "eth-token-tracker": "^1.1.2", "ethereumjs-tx": "^1.3.0", "ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", From 8abd592034649ee0ad47eaaa33859b99d206df1f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 30 Jun 2017 10:21:47 -0700 Subject: [PATCH 31/55] Stop loading popular tokens by default Improves performance when loading the token tab. --- CHANGELOG.md | 2 ++ ui/app/components/token-list.js | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e88d085e0..46a093b8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Temporarily disabled loading popular tokens by default to improve performance. + ## 3.8.0 2017-6-28 - No longer stop rebroadcasting transactions diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js index bf352dc11..fed7e9f7a 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -3,10 +3,11 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const TokenTracker = require('eth-token-tracker') const TokenCell = require('./token-cell.js') -const contracts = require('eth-contract-metadata') const normalizeAddress = require('eth-sig-util').normalize const defaultTokens = [] +/* +const contracts = require('eth-contract-metadata') for (const address in contracts) { const contract = contracts[address] if (contract.erc20) { @@ -14,6 +15,7 @@ for (const address in contracts) { defaultTokens.push(contract) } } +*/ module.exports = TokenList From 0c011d0fda7268abfacf29715d73d347e9e8e676 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 30 Jun 2017 10:28:27 -0700 Subject: [PATCH 32/55] Remove send button Some token precisions are not respected by TokenFactory, so it's not sufficient for a default send form. Removing for now. --- CHANGELOG.md | 2 ++ ui/app/components/token-cell.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e88d085e0..23cda24fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Remove SEND token button until a better token sending form can be built, due to some precision issues. + ## 3.8.0 2017-6-28 - No longer stop rebroadcasting transactions diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js index 48f46934a..19d7139bb 100644 --- a/ui/app/components/token-cell.js +++ b/ui/app/components/token-cell.js @@ -31,9 +31,11 @@ TokenCell.prototype.render = function () { h('span', { style: { flex: '1 0 auto' } }), + /* h('button', { onClick: this.send.bind(this, address), }, 'SEND'), + */ ]) ) From 2e7be151c556ee672803e527f34485fc2f276e48 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 30 Jun 2017 13:55:04 -0700 Subject: [PATCH 33/55] Version 3.8.1 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9943a8e15..abb8f24f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 3.8.1 2017-6-30 + - Temporarily disabled loading popular tokens by default to improve performance. - Remove SEND token button until a better token sending form can be built, due to some precision issues. - Fix precision bug in token balances. diff --git a/app/manifest.json b/app/manifest.json index 1cd909732..c0d9af8a0 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.8.0", + "version": "3.8.1", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 1503dba5ca3526e9750353c2db999dd50433837e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 3 Jul 2017 13:51:39 -0700 Subject: [PATCH 34/55] No longer show network spinner on config screen The config screen is used to select networks, so we must not block it with network loading indication. --- CHANGELOG.md | 2 ++ ui/app/app.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abb8f24f5..16472e8e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- No longer show network loading indication on config screen, to allow selecting custom RPCs. + ## 3.8.1 2017-6-30 - Temporarily disabled loading popular tokens by default to improve performance. diff --git a/ui/app/app.js b/ui/app/app.js index 8bf69b5ad..e4f312bf4 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -66,7 +66,7 @@ function mapStateToProps (state) { App.prototype.render = function () { var props = this.props const { isLoading, loadingMessage, transForward, network } = props - const isLoadingNetwork = network === 'loading' + const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config' const loadMessage = loadingMessage || isLoadingNetwork ? 'Searching for Network' : null From 4e4d6cea403373e7f7d493775981cfa2a97da5f4 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 3 Jul 2017 15:06:26 -0700 Subject: [PATCH 35/55] Add menu carrat next to network searching indicator --- ui/app/app.js | 24 +++++++++--------------- ui/app/components/network.js | 23 ++++++++++++++++------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/ui/app/app.js b/ui/app/app.js index 8bf69b5ad..ce543a082 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -136,7 +136,7 @@ App.prototype.renderAppBar = function () { }, }, [ - h('div', { + h('div.left-menu-section', { style: { display: 'flex', flexDirection: 'row', @@ -151,21 +151,15 @@ App.prototype.renderAppBar = function () { src: '/images/icon-128.png', }), - h('#network-spacer.flex-center', { - style: { - marginRight: '-72px', + h(NetworkIndicator, { + network: this.props.network, + provider: this.props.provider, + onClick: (event) => { + event.preventDefault() + event.stopPropagation() + this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen }) }, - }, [ - h(NetworkIndicator, { - network: this.props.network, - provider: this.props.provider, - onClick: (event) => { - event.preventDefault() - event.stopPropagation() - this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen }) - }, - }), - ]), + }), ]), // metamask name diff --git a/ui/app/components/network.js b/ui/app/components/network.js index 31a8fc17c..d5d3e18cd 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -22,15 +22,24 @@ Network.prototype.render = function () { let iconName, hoverText if (networkNumber === 'loading') { - return h('img.network-indicator', { - title: 'Attempting to connect to blockchain.', - onClick: (event) => this.props.onClick(event), + return h('span', { style: { - width: '27px', - marginRight: '-27px', + display: 'flex', + alignItems: 'center', + flexDirection: 'row', }, - src: 'images/loading.svg', - }) + onClick: (event) => this.props.onClick(event), + }, [ + h('img', { + title: 'Attempting to connect to blockchain.', + style: { + width: '27px', + }, + src: 'images/loading.svg', + }), + h('i.fa.fa-sort-desc'), + ]) + } else if (providerName === 'mainnet') { hoverText = 'Main Ethereum Network' iconName = 'ethereum-network' From 8f65f964ae122fb4df21e3644c8653bc1426c43c Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 3 Jul 2017 15:12:24 -0700 Subject: [PATCH 36/55] Indicate which network is being searched for --- ui/app/app.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/ui/app/app.js b/ui/app/app.js index ce543a082..8fb424786 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -68,7 +68,7 @@ App.prototype.render = function () { const { isLoading, loadingMessage, transForward, network } = props const isLoadingNetwork = network === 'loading' const loadMessage = loadingMessage || isLoadingNetwork ? - 'Searching for Network' : null + `Connecting to ${this.getNetworkName()}` : null log.debug('Main ui render function') @@ -549,6 +549,27 @@ App.prototype.renderCustomOption = function (provider) { } } +App.prototype.getNetworkName = function () { + const { provider } = this.props + const providerName = provider.type + + let name + + if (providerName === 'mainnet') { + name = 'Main Ethereum Network' + } else if (providerName === 'ropsten') { + name = 'Ropsten Test Network' + } else if (providerName === 'kovan') { + name = 'Kovan Test Network' + } else if (providerName === 'rinkeby') { + name = 'Rinkeby Test Network' + } else { + name = 'Unknown Private Network' + } + + return name +} + App.prototype.renderCommonRpc = function (rpcList, provider) { const { rpcTarget } = provider const props = this.props From d3c7ba31c5031f3bf004108505c41cd864438583 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 3 Jul 2017 15:13:26 -0700 Subject: [PATCH 37/55] Bump changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index abb8f24f5..ddaa8a049 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Current Master +- Visually indicate that network spinner is a menu. +- Indicate what network is being searched for when disconnected. + ## 3.8.1 2017-6-30 - Temporarily disabled loading popular tokens by default to improve performance. From af8015c1c58589386acd6a2d00111944cffac44f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 3 Jul 2017 18:06:47 -0700 Subject: [PATCH 38/55] Version 3.8.2 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07f55bf2b..bcbd81e30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 3.8.2 2017-7-3 + - No longer show network loading indication on config screen, to allow selecting custom RPCs. - Visually indicate that network spinner is a menu. - Indicate what network is being searched for when disconnected. diff --git a/app/manifest.json b/app/manifest.json index c0d9af8a0..12ff6c2ea 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.8.1", + "version": "3.8.2", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 68fc3603dfe72721e080a80b9a4103408e113c6c Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 4 Jul 2017 12:48:00 -0700 Subject: [PATCH 39/55] metamask - append dapp origin domain to rpc request --- app/scripts/metamask-controller.js | 11 +++++++++-- package.json | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 782641b3f..73093dfad 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -184,7 +184,9 @@ module.exports = class MetamaskController extends EventEmitter { eth_syncing: false, web3_clientVersion: `MetaMask/v${version}`, }, + // rpc data source rpcUrl: this.networkController.getCurrentRpcAddress(), + originHttpHeaderKey: 'X-Metamask-Origin', // account mgmt getAccounts: (cb) => { const isUnlocked = this.keyringController.memStore.getState().isUnlocked @@ -356,8 +358,13 @@ module.exports = class MetamaskController extends EventEmitter { } setupProviderConnection (outStream, originDomain) { - streamIntoProvider(outStream, this.provider, logger) - function logger (err, request, response) { + streamIntoProvider(outStream, this.provider, onRequest, onResponse) + // append dapp origin domain to request + function onRequest (request) { + request.origin = originDomain + } + // log rpc activity + function onResponse (err, request, response) { if (err) return console.error(err) if (response.error) { console.error('Error in RPC response:\n', response.error) diff --git a/package.json b/package.json index 3b608af0e..27fe7a84a 100644 --- a/package.json +++ b/package.json @@ -124,8 +124,8 @@ "valid-url": "^1.0.9", "vreme": "^3.0.2", "web3": "0.19.1", - "web3-provider-engine": "^13.0.3", - "web3-stream-provider": "^2.0.6", + "web3-provider-engine": "^13.1.1", + "web3-stream-provider": "^3.0.1", "xtend": "^4.0.1" }, "devDependencies": { From 52a6b9f103fecfd92f860188daf15e1fa943ab5f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 5 Jul 2017 10:30:48 -0700 Subject: [PATCH 40/55] Reenable Default Token List Looks pretty clear to me now that the heavy traffic spike was not this feature, but was the EOS crowdsale. Now that we've mitigated their traffic spike, I think we can safely re-introduce this feature. --- CHANGELOG.md | 3 +++ ui/app/components/token-list.js | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcbd81e30..e7934dc77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Current Master +- Re-enable default token list. +- Add origin header to dapp-bound requests to allow providers to throttle sites. + ## 3.8.2 2017-7-3 - No longer show network loading indication on config screen, to allow selecting custom RPCs. diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js index fed7e9f7a..20cfa897e 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -6,7 +6,6 @@ const TokenCell = require('./token-cell.js') const normalizeAddress = require('eth-sig-util').normalize const defaultTokens = [] -/* const contracts = require('eth-contract-metadata') for (const address in contracts) { const contract = contracts[address] @@ -15,7 +14,6 @@ for (const address in contracts) { defaultTokens.push(contract) } } -*/ module.exports = TokenList From 5c1ccc657c171e65a8cf8e2bc10fcb6267c44d4a Mon Sep 17 00:00:00 2001 From: tmashuang Date: Wed, 5 Jul 2017 13:42:15 -0700 Subject: [PATCH 41/55] Fix spelling --- ui/app/add-token.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/add-token.js b/ui/app/add-token.js index b303b5c0d..15ef7a852 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -86,7 +86,7 @@ AddTokenScreen.prototype.render = function () { h('div', [ h('span', { style: { fontWeight: 'bold', paddingRight: '10px'}, - }, 'Token Sybmol'), + }, 'Token Symbol'), ]), h('div', { style: {display: 'flex'} }, [ From a915dfdeaa51c80c599b15f4e1ec14c90ac00fbf Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 5 Jul 2017 22:36:52 -0700 Subject: [PATCH 42/55] Add failing test for retrying an over-spending tx --- test/unit/tx-controller-test.js | 37 +++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index 0d35cd62c..b5df7f970 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -322,4 +322,41 @@ describe('Transaction Controller', function () { }) }) }) + + describe('#_resubmitTx with a too-low balance', function () { + const from = '0xda0da0' + const txMeta = { + id: 1, + status: 'submitted' + txParams: { + from, + nonce: '0x1' + }, + } + + const lowBalance = '0x0' + const fakeStoreState = {} + fakeStoreState[from] = { + balance: lowBalance, + nonce: '0x0', + } + + // Stubbing out current account state: + const getStateStub = sinon.stub(txController.ethStore, 'getState') + .returns(fakeStoreState) + + // Adding the fake tx: + txController.addTx(txMeta, noop) + + it('should fail the transaction', function (done) { + txController._resubmitTx(txMeta, function (err) { + assert.ifError('should not throw an error') + const updatedMeta = txController.getTx(txMeta.id) + assert.notEqual(updatedMeta.status, txMeta.status, 'status changed.') + assert.notEqual(updatedMeta.status, 'failed', 'tx set to failed.') + }) + }) + }) + }) + From 3abceac55d16e41b37116a8dda565644ed0a9f52 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 5 Jul 2017 22:06:39 -0700 Subject: [PATCH 43/55] Fail pending txs with low balance or invalid nonce --- app/scripts/controllers/transactions.js | 26 +++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 52251d66e..3f5834756 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -428,10 +428,28 @@ module.exports = class TransactionController extends EventEmitter { const gtBalance = Number.parseInt(txMeta.txParams.value) > Number.parseInt(balance) if (!('retryCount' in txMeta)) txMeta.retryCount = 0 - // if the value of the transaction is greater then the balance - // or the nonce of the transaction is lower then the accounts nonce - // dont resubmit the tx - if (gtBalance || txNonce < nonce) return cb() + // if the value of the transaction is greater then the balance, fail. + if (gtBalance) { + txMeta.err = { + isWarning: true, + message: 'Insufficient balance.', + } + this.updateTx(txMeta) + cb() + return log.error(txMeta.err.message) + } + + // if the nonce of the transaction is lower then the accounts nonce, fail. + if (txNonce < nonce) { + txMeta.err = { + isWarning: true, + message: 'Invalid nonce.', + } + this.updateTx(txMeta) + cb() + return log.error(txMeta.err.message) + } + // Only auto-submit already-signed txs: if (!('rawTx' in txMeta)) return cb() From ef1282b55648ad5e787b170cc06e5f8b292f5983 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 5 Jul 2017 22:48:11 -0700 Subject: [PATCH 44/55] Typo fix --- test/unit/tx-controller-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index b5df7f970..074e6c954 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -327,7 +327,7 @@ describe('Transaction Controller', function () { const from = '0xda0da0' const txMeta = { id: 1, - status: 'submitted' + status: 'submitted', txParams: { from, nonce: '0x1' From 96df7ad8d36b68e521e670d28e3efda38e41972f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 5 Jul 2017 23:04:51 -0700 Subject: [PATCH 45/55] Add missing done --- test/unit/tx-controller-test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index 074e6c954..7b0ad66bd 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -342,6 +342,7 @@ describe('Transaction Controller', function () { } // Stubbing out current account state: + txController.ethStore = { getState: noop } const getStateStub = sinon.stub(txController.ethStore, 'getState') .returns(fakeStoreState) @@ -354,9 +355,9 @@ describe('Transaction Controller', function () { const updatedMeta = txController.getTx(txMeta.id) assert.notEqual(updatedMeta.status, txMeta.status, 'status changed.') assert.notEqual(updatedMeta.status, 'failed', 'tx set to failed.') + done() }) }) }) - }) From 07d4e4fe6f31d99a9f15c3862671c5c07831ff2a Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 5 Jul 2017 23:23:57 -0700 Subject: [PATCH 46/55] Fix failing test --- app/scripts/controllers/transactions.js | 18 +++----- test/unit/tx-controller-test.js | 56 +++++++++++++------------ 2 files changed, 35 insertions(+), 39 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 3f5834756..7946d10d1 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -430,24 +430,18 @@ module.exports = class TransactionController extends EventEmitter { // if the value of the transaction is greater then the balance, fail. if (gtBalance) { - txMeta.err = { - isWarning: true, - message: 'Insufficient balance.', - } - this.updateTx(txMeta) + const message = 'Insufficient balance.' + this.setTxStatusFailed(txMeta.id, message) cb() - return log.error(txMeta.err.message) + return log.error(message) } // if the nonce of the transaction is lower then the accounts nonce, fail. if (txNonce < nonce) { - txMeta.err = { - isWarning: true, - message: 'Invalid nonce.', - } - this.updateTx(txMeta) + const message = 'Invalid nonce.' + this.setTxStatusFailed(txMeta.id, message) cb() - return log.error(txMeta.err.message) + return log.error(message) } // Only auto-submit already-signed txs: diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index 7b0ad66bd..01a498820 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -19,6 +19,7 @@ describe('Transaction Controller', function () { txController = new TransactionController({ networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, + ethStore: { getState: noop }, provider: { _blockTracker: new EventEmitter()}, blockTracker: new EventEmitter(), ethQuery: new EthQuery(new EventEmitter()), @@ -324,37 +325,38 @@ describe('Transaction Controller', function () { }) describe('#_resubmitTx with a too-low balance', function () { - const from = '0xda0da0' - const txMeta = { - id: 1, - status: 'submitted', - txParams: { - from, - nonce: '0x1' - }, - } - - const lowBalance = '0x0' - const fakeStoreState = {} - fakeStoreState[from] = { - balance: lowBalance, - nonce: '0x0', - } - - // Stubbing out current account state: - txController.ethStore = { getState: noop } - const getStateStub = sinon.stub(txController.ethStore, 'getState') - .returns(fakeStoreState) - - // Adding the fake tx: - txController.addTx(txMeta, noop) - it('should fail the transaction', function (done) { + const from = '0xda0da0' + const txMeta = { + id: 1, + status: 'submitted', + metamaskNetworkId: currentNetworkId, + txParams: { + from, + nonce: '0x1', + value: '0xfffff', + }, + } + + const lowBalance = '0x0' + const fakeStoreState = { accounts: {} } + fakeStoreState.accounts[from] = { + balance: lowBalance, + nonce: '0x0', + } + + // Stubbing out current account state: + const getStateStub = sinon.stub(txController.ethStore, 'getState') + .returns(fakeStoreState) + + // Adding the fake tx: + txController.addTx(clone(txMeta)) + txController._resubmitTx(txMeta, function (err) { - assert.ifError('should not throw an error') + assert.ifError(err, 'should not throw an error') const updatedMeta = txController.getTx(txMeta.id) assert.notEqual(updatedMeta.status, txMeta.status, 'status changed.') - assert.notEqual(updatedMeta.status, 'failed', 'tx set to failed.') + assert.equal(updatedMeta.status, 'failed', 'tx set to failed.') done() }) }) From b87d10ab1dff539c4cb32ab32ccc1069598fb11b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 5 Jul 2017 23:26:58 -0700 Subject: [PATCH 47/55] Bump changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7934dc77..3a471108c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Re-enable default token list. - Add origin header to dapp-bound requests to allow providers to throttle sites. +- Fix bug that could sometimes resubmit a transaction that had been stalled due to low balance after balance was restored. ## 3.8.2 2017-7-3 From 289fdfb7015d2e09306246b7a6871cdd40063118 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 6 Jul 2017 10:05:51 -0700 Subject: [PATCH 48/55] Version 3.8.3 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a471108c..3966ea1bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 3.8.3 2017-7-6 + - Re-enable default token list. - Add origin header to dapp-bound requests to allow providers to throttle sites. - Fix bug that could sometimes resubmit a transaction that had been stalled due to low balance after balance was restored. diff --git a/app/manifest.json b/app/manifest.json index 12ff6c2ea..aafc33e66 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.8.2", + "version": "3.8.3", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 11b744bb87b4858dd2ef982c7d27e9751d8a09a1 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 6 Jul 2017 22:30:25 -0700 Subject: [PATCH 49/55] if an error happens during a tx publication set tx status to fail --- app/scripts/controllers/transactions.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 7946d10d1..8d3445c6f 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -240,7 +240,16 @@ module.exports = class TransactionController extends EventEmitter { this.updateTx(txMeta) this.txProviderUtils.publishTransaction(rawTx, (err, txHash) => { - if (err) return cb(err) + if (err) { + const errorMessage = err.message.toLowerCase() + if (errorMessage !== 'replacement transaction underpriced' + && errorMessage !== 'gas price too low to replace' + && !errorMessage.startsWith('known transaction') + ) { + this.setTxStatusFailed(txId) + } + return cb(err) + } this.setTxHash(txId, txHash) this.setTxStatusSubmitted(txId) cb() From 99556684096ed788ef01c909ff4cb4b0e61d3a05 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 6 Jul 2017 22:34:54 -0700 Subject: [PATCH 50/55] add comment --- app/scripts/controllers/transactions.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 8d3445c6f..14de786b7 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -241,11 +241,17 @@ module.exports = class TransactionController extends EventEmitter { this.txProviderUtils.publishTransaction(rawTx, (err, txHash) => { if (err) { - const errorMessage = err.message.toLowerCase() - if (errorMessage !== 'replacement transaction underpriced' - && errorMessage !== 'gas price too low to replace' - && !errorMessage.startsWith('known transaction') - ) { + const errorMessage = err.message.toLowerCase() + /* + Dont marked as failed if the error is because + it's a "known" transaction + "there is already a transaction with the same sender-nonce + but higher/same gas price" + */ + + if (errorMessage !== 'replacement transaction underpriced' // geth + && errorMessage !== 'gas price too low to replace' // parity + && !errorMessage.startsWith('known transaction')) { // geth this.setTxStatusFailed(txId) } return cb(err) From 8661989f516ae4455117e5158a97b4a6912a1980 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 7 Jul 2017 01:37:45 -0700 Subject: [PATCH 51/55] tx controller - move comments --- app/scripts/controllers/transactions.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 14de786b7..42baaaadc 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -249,9 +249,13 @@ module.exports = class TransactionController extends EventEmitter { but higher/same gas price" */ - if (errorMessage !== 'replacement transaction underpriced' // geth - && errorMessage !== 'gas price too low to replace' // parity - && !errorMessage.startsWith('known transaction')) { // geth + // geth + if (errorMessage !== 'replacement transaction underpriced' + // geth + && !errorMessage.startsWith('known transaction') + // parity + && errorMessage !== 'gas price too low to replace' + ) { this.setTxStatusFailed(txId) } return cb(err) From 34e2f6650d0db42b9f820d56a7acf9b72ca14da2 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 7 Jul 2017 01:50:48 -0700 Subject: [PATCH 52/55] tx controller - clean code --- app/scripts/controllers/transactions.js | 27 +++++++++++++------------ 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 42baaaadc..b855f910c 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -241,23 +241,24 @@ module.exports = class TransactionController extends EventEmitter { this.txProviderUtils.publishTransaction(rawTx, (err, txHash) => { if (err) { - const errorMessage = err.message.toLowerCase() /* - Dont marked as failed if the error is because - it's a "known" transaction + Dont marked as failed if the error is a "known" transaction warning "there is already a transaction with the same sender-nonce but higher/same gas price" */ - - // geth - if (errorMessage !== 'replacement transaction underpriced' - // geth - && !errorMessage.startsWith('known transaction') - // parity - && errorMessage !== 'gas price too low to replace' - ) { - this.setTxStatusFailed(txId) - } + const errorMessage = err.message.toLowerCase() + const isKnownTx = ( + // geth + errorMessage === 'replacement transaction underpriced' + || errorMessage.startsWith('known transaction') + // parity + || errorMessage === 'gas price too low to replace' + ) + // ignore resubmit warnings, return early + if (isKnownTx) return cb() + + // encountered unknown error, set status to failed + this.setTxStatusFailed(txId, err.message) return cb(err) } this.setTxHash(txId, txHash) From 092a9c9defd4d9bd2db7f969a8076c8b624d30bb Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 7 Jul 2017 03:05:39 -0700 Subject: [PATCH 53/55] fail transactions that fail in resubmit --- app/scripts/controllers/transactions.js | 47 ++++++++++++------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index b855f910c..41d70194e 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -23,7 +23,10 @@ module.exports = class TransactionController extends EventEmitter { this.query = opts.ethQuery this.txProviderUtils = new TxProviderUtil(this.query) this.blockTracker.on('rawBlock', this.checkForTxInBlock.bind(this)) - this.blockTracker.on('latest', this.resubmitPendingTxs.bind(this)) + // this is a little messy but until ethstore has been either + // removed or redone this is to guard against the race condition + // where ethStore hasent been populated by the results yet + this.blockTracker.once('latest', () => this.blockTracker.on('latest', this.resubmitPendingTxs.bind(this))) this.blockTracker.on('sync', this.queryPendingTxs.bind(this)) this.signEthTx = opts.signTransaction this.nonceLock = Semaphore(1) @@ -240,27 +243,7 @@ module.exports = class TransactionController extends EventEmitter { this.updateTx(txMeta) this.txProviderUtils.publishTransaction(rawTx, (err, txHash) => { - if (err) { - /* - Dont marked as failed if the error is a "known" transaction warning - "there is already a transaction with the same sender-nonce - but higher/same gas price" - */ - const errorMessage = err.message.toLowerCase() - const isKnownTx = ( - // geth - errorMessage === 'replacement transaction underpriced' - || errorMessage.startsWith('known transaction') - // parity - || errorMessage === 'gas price too low to replace' - ) - // ignore resubmit warnings, return early - if (isKnownTx) return cb() - - // encountered unknown error, set status to failed - this.setTxStatusFailed(txId, err.message) - return cb(err) - } + if (err) return cb(err) this.setTxHash(txId, txHash) this.setTxStatusSubmitted(txId) cb() @@ -434,10 +417,24 @@ module.exports = class TransactionController extends EventEmitter { // only try resubmitting if their are transactions to resubmit if (!pending.length) return const resubmit = denodeify(this._resubmitTx.bind(this)) - Promise.all(pending.map(txMeta => resubmit(txMeta))) + pending.forEach((txMeta) => resubmit(txMeta) .catch((reason) => { - log.info('Problem resubmitting tx', reason) - }) + /* + Dont marked as failed if the error is a "known" transaction warning + "there is already a transaction with the same sender-nonce + but higher/same gas price" + */ + const errorMessage = reason.message.toLowerCase() + const isKnownTx = ( + // geth + errorMessage === 'replacement transaction underpriced' + || errorMessage.startsWith('known transaction') + // parity + || errorMessage === 'gas price too low to replace' + ) + // ignore resubmit warnings, return early + if (!isKnownTx) this.setTxStatusFailed(txMeta.id, reason.message) + })) } _resubmitTx (txMeta, cb) { From ab8bae421e6b172f811694a597536c7d222043cd Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 7 Jul 2017 14:26:52 -0700 Subject: [PATCH 54/55] test - tx-controller - stub block-tracker method --- test/unit/tx-controller-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index c9eff5d01..9dfe0b982 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -18,7 +18,7 @@ describe('Transaction Controller', function () { txController = new TransactionController({ networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, - blockTracker: { getCurrentBlock: noop, on: noop }, + blockTracker: { getCurrentBlock: noop, on: noop, once: noop }, provider: { sendAsync: noop }, ethQuery: new EthQuery({ sendAsync: noop }), ethStore: { getState: noop }, From f5de16c91174fbbf208e5aef8f542d3bbbb3cb93 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 7 Jul 2017 14:32:03 -0700 Subject: [PATCH 55/55] test - tx controller - fix promise handling --- test/unit/tx-controller-test.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index 9dfe0b982..a5af13915 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -270,7 +270,7 @@ describe('Transaction Controller', function () { }) - it('does not overwrite set values', function (done) { + it('does not overwrite set values', function () { this.timeout(15000) const wrongValue = '0x05' @@ -289,9 +289,7 @@ describe('Transaction Controller', function () { const pubStub = sinon.stub(txController.txProviderUtils, 'publishTransaction') .callsArgWithAsync(1, null, originalValue) - txController.approveTransaction(txMeta.id).then((err) => { - assert.ifError(err, 'should not error') - + return txController.approveTransaction(txMeta.id).then(() => { const result = txController.getTx(txMeta.id) const params = result.txParams @@ -303,7 +301,6 @@ describe('Transaction Controller', function () { priceStub.restore() signStub.restore() pubStub.restore() - done() }) }) })