diff options
Diffstat (limited to 'ui')
31 files changed, 476 insertions, 124 deletions
diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index a844daf88..d4f707e0b 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -121,6 +121,7 @@ AccountDetailScreen.prototype.render = function () { overflow: 'hidden', textOverflow: 'ellipsis', padding: '5px 0px', + lineHeight: '25px', }, }, [ identity && identity.name, diff --git a/ui/app/actions.js b/ui/app/actions.js index 3ea092e57..04fd35b20 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -97,6 +97,8 @@ var actions = { cancelMsg: cancelMsg, signPersonalMsg, cancelPersonalMsg, + signTypedMsg, + cancelTypedMsg, signTx: signTx, updateAndApproveTx, cancelTx: cancelTx, @@ -131,9 +133,12 @@ var actions = { showLoadingIndication: showLoadingIndication, hideLoadingIndication: hideLoadingIndication, // buy Eth with coinbase + onboardingBuyEthView, + ONBOARDING_BUY_ETH_VIEW: 'ONBOARDING_BUY_ETH_VIEW', BUY_ETH: 'BUY_ETH', buyEth: buyEth, buyEthView: buyEthView, + buyWithShapeShift, BUY_ETH_VIEW: 'BUY_ETH_VIEW', COINBASE_SUBVIEW: 'COINBASE_SUBVIEW', coinBaseSubview: coinBaseSubview, @@ -213,14 +218,18 @@ function confirmSeedWords () { return (dispatch) => { dispatch(actions.showLoadingIndication()) log.debug(`background.clearSeedWordCache`) - background.clearSeedWordCache((err, account) => { - dispatch(actions.hideLoadingIndication()) - if (err) { - return dispatch(actions.displayWarning(err.message)) - } + return new Promise((resolve, reject) => { + background.clearSeedWordCache((err, account) => { + dispatch(actions.hideLoadingIndication()) + if (err) { + dispatch(actions.displayWarning(err.message)) + reject(err) + } - log.info('Seed word cache cleared. ' + account) - dispatch(actions.showAccountDetail(account)) + log.info('Seed word cache cleared. ' + account) + dispatch(actions.showAccountsPage()) + resolve(account) + }) }) } } @@ -229,10 +238,20 @@ function createNewVaultAndRestore (password, seed) { return (dispatch) => { dispatch(actions.showLoadingIndication()) log.debug(`background.createNewVaultAndRestore`) - background.createNewVaultAndRestore(password, seed, (err) => { - dispatch(actions.hideLoadingIndication()) - if (err) return dispatch(actions.displayWarning(err.message)) - dispatch(actions.showAccountsPage()) + + return new Promise((resolve, reject) => { + background.createNewVaultAndRestore(password, seed, (err) => { + + dispatch(actions.hideLoadingIndication()) + + if (err) { + dispatch(actions.displayWarning(err.message)) + return reject(err) + } + + dispatch(actions.showAccountsPage()) + resolve() + }) }) } } @@ -241,19 +260,26 @@ function createNewVaultAndKeychain (password) { return (dispatch) => { dispatch(actions.showLoadingIndication()) log.debug(`background.createNewVaultAndKeychain`) - background.createNewVaultAndKeychain(password, (err) => { - if (err) { - return dispatch(actions.displayWarning(err.message)) - } - log.debug(`background.placeSeedWords`) - background.placeSeedWords((err) => { + + return new Promise((resolve, reject) => { + background.createNewVaultAndKeychain(password, (err) => { if (err) { - return dispatch(actions.displayWarning(err.message)) + dispatch(actions.displayWarning(err.message)) + return reject(err) } - dispatch(actions.hideLoadingIndication()) - forceUpdateMetamaskState(dispatch) + log.debug(`background.placeSeedWords`) + background.placeSeedWords((err) => { + if (err) { + dispatch(actions.displayWarning(err.message)) + return reject(err) + } + dispatch(actions.hideLoadingIndication()) + forceUpdateMetamaskState(dispatch) + resolve() + }) }) }) + } } @@ -297,18 +323,25 @@ function importNewAccount (strategy, args) { return (dispatch) => { dispatch(actions.showLoadingIndication('This may take a while, be patient.')) log.debug(`background.importAccountWithStrategy`) - background.importAccountWithStrategy(strategy, args, (err) => { - if (err) return dispatch(actions.displayWarning(err.message)) - log.debug(`background.getState`) - background.getState((err, newState) => { - dispatch(actions.hideLoadingIndication()) + return new Promise((resolve, reject) => { + background.importAccountWithStrategy(strategy, args, (err) => { if (err) { - return dispatch(actions.displayWarning(err.message)) + dispatch(actions.displayWarning(err.message)) + return reject(err) } - dispatch(actions.updateMetamaskState(newState)) - dispatch({ - type: actions.SHOW_ACCOUNT_DETAIL, - value: newState.selectedAddress, + log.debug(`background.getState`) + background.getState((err, newState) => { + dispatch(actions.hideLoadingIndication()) + if (err) { + dispatch(actions.displayWarning(err.message)) + return reject(err) + } + dispatch(actions.updateMetamaskState(newState)) + dispatch({ + type: actions.SHOW_ACCOUNT_DETAIL, + value: newState.selectedAddress, + }) + resolve(newState) }) }) }) @@ -392,6 +425,25 @@ function signPersonalMsg (msgData) { } } +function signTypedMsg (msgData) { + log.debug('action - signTypedMsg') + return (dispatch) => { + dispatch(actions.showLoadingIndication()) + + log.debug(`actions calling background.signTypedMessage`) + background.signTypedMessage(msgData, (err, newState) => { + log.debug('signTypedMessage called back') + dispatch(actions.updateMetamaskState(newState)) + dispatch(actions.hideLoadingIndication()) + + if (err) log.error(err) + if (err) return dispatch(actions.displayWarning(err.message)) + + dispatch(actions.completedTx(msgData.metamaskId)) + }) + } +} + function signTx (txData) { return (dispatch) => { dispatch(actions.showLoadingIndication()) @@ -446,6 +498,12 @@ function cancelPersonalMsg (msgData) { return actions.completedTx(id) } +function cancelTypedMsg (msgData) { + const id = msgData.id + background.cancelTypedMessage(id) + return actions.completedTx(id) +} + function cancelTx (txData) { return (dispatch) => { log.debug(`background.cancelTransaction`) @@ -662,21 +720,23 @@ function goBackToInitView () { function markNoticeRead (notice) { return (dispatch) => { - dispatch(this.showLoadingIndication()) + dispatch(actions.showLoadingIndication()) log.debug(`background.markNoticeRead`) - background.markNoticeRead(notice, (err, notice) => { - dispatch(this.hideLoadingIndication()) - if (err) { - return dispatch(actions.displayWarning(err)) - } - if (notice) { - return dispatch(actions.showNotice(notice)) - } else { - dispatch(this.clearNotices()) - return { - type: actions.SHOW_ACCOUNTS_PAGE, + return new Promise((resolve, reject) => { + background.markNoticeRead(notice, (err, notice) => { + dispatch(actions.hideLoadingIndication()) + if (err) { + dispatch(actions.displayWarning(err)) + return reject(err) } - } + if (notice) { + dispatch(actions.showNotice(notice)) + resolve() + } else { + dispatch(actions.clearNotices()) + resolve() + } + }) }) } } @@ -856,6 +916,13 @@ function buyEth (opts) { } } +function onboardingBuyEthView (address) { + return { + type: actions.ONBOARDING_BUY_ETH_VIEW, + value: address, + } +} + function buyEthView (address) { return { type: actions.BUY_ETH_VIEW, @@ -921,6 +988,18 @@ function coinShiftRquest (data, marketData) { } } +function buyWithShapeShift (data) { + return dispatch => new Promise((resolve, reject) => { + shapeShiftRequest('shift', { method: 'POST', data}, (response) => { + if (response.error) { + return reject(response.error) + } + background.createShapeShiftTx(response.deposit, response.depositType) + return resolve(response) + }) + }) +} + function showQrView (data, message) { return { type: actions.SHOW_QR_VIEW, @@ -954,9 +1033,14 @@ function shapeShiftRequest (query, options, cb) { options.method ? method = options.method : method = 'GET' var requestListner = function (request) { - queryResponse = JSON.parse(this.responseText) - cb ? cb(queryResponse) : null - return queryResponse + try { + queryResponse = JSON.parse(this.responseText) + cb ? cb(queryResponse) : null + return queryResponse + } catch (e) { + cb ? cb({error: e}) : null + return e + } } var shapShiftReq = new XMLHttpRequest() diff --git a/ui/app/add-token.js b/ui/app/add-token.js index 18adc7eb5..9354a4cad 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -73,7 +73,7 @@ AddTokenScreen.prototype.render = function () { }, [ h('a', { style: { fontWeight: 'bold', paddingRight: '10px'}, - href: 'https://consensyssupport.happyfox.com/staff/kb/article/24-what-is-a-token-contract-address', + href: 'https://support.metamask.io/kb/article/24-what-is-a-token-contract-address', target: '_blank', }, [ h('span', 'Token Contract Address '), diff --git a/ui/app/app.js b/ui/app/app.js index 613577913..bd0ccb0a2 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -3,6 +3,9 @@ const Component = require('react').Component const connect = require('react-redux').connect const h = require('react-hyperscript') const actions = require('./actions') +// mascara +const MascaraFirstTime = require('../../mascara/src/app/first-time').default +const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default // init const InitializeMenuScreen = require('./first-time/init-menu') const NewKeyChainScreen = require('./new-keychain') @@ -43,6 +46,9 @@ function mapStateToProps (state) { accounts, address, keyrings, + isInitialized, + noActiveNotices, + seedWords, } = state.metamask const selected = address || Object.keys(accounts)[0] @@ -56,6 +62,8 @@ function mapStateToProps (state) { currentView: state.appState.currentView, activeAddress: state.appState.activeAddress, transForward: state.appState.transForward, + isMascara: state.metamask.isMascara, + isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized), seedWords: state.metamask.seedWords, unapprovedTxs: state.metamask.unapprovedTxs, unapprovedMsgs: state.metamask.unapprovedMsgs, @@ -98,10 +106,7 @@ App.prototype.render = function () { this.renderNetworkDropdown(), this.renderDropdown(), - h(Loading, { - isLoading: isLoading || isLoadingNetwork, - loadingMessage: loadMessage, - }), + this.renderLoadingIndicator({ isLoading, isLoadingNetwork, loadMessage }), // panel content h('.app-primary' + (transForward ? '.from-right' : '.from-left'), { @@ -123,6 +128,17 @@ App.prototype.renderAppBar = function () { const props = this.props const state = this.state || {} const isNetworkMenuOpen = state.isNetworkMenuOpen || false + const {isMascara, isOnboarding} = props + + // Do not render header if user is in mascara onboarding + if (isMascara && isOnboarding) { + return null + } + + // Do not render header if user is in mascara buy ether + if (isMascara && props.currentView.name === 'buyEth') { + return null + } return ( @@ -388,6 +404,17 @@ App.prototype.renderDropdown = function () { ]) } +App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork, loadMessage }) { + const { isMascara } = this.props + + return isMascara + ? null + : h(Loading, { + isLoading: isLoading || isLoadingNetwork, + loadingMessage: loadMessage, + }) +} + App.prototype.renderBackButton = function (style, justArrow = false) { var props = this.props return ( @@ -410,6 +437,11 @@ App.prototype.renderBackButton = function (style, justArrow = false) { App.prototype.renderPrimary = function () { log.debug('rendering primary') var props = this.props + const {isMascara, isOnboarding} = props + + if (isMascara && isOnboarding) { + return h(MascaraFirstTime) + } // notices if (!props.noActiveNotices) { @@ -510,6 +542,10 @@ App.prototype.renderPrimary = function () { log.debug('rendering buy ether screen') return h(BuyView, {key: 'buyEthView'}) + case 'onboardingBuyEth': + log.debug('rendering onboarding buy ether screen') + return h(MascaraBuyEtherScreen, {key: 'buyEthView'}) + case 'qr': log.debug('rendering show qr screen') return h('div', { diff --git a/ui/app/components/account-dropdowns.js b/ui/app/components/account-dropdowns.js index b087a40d4..0c34a5154 100644 --- a/ui/app/components/account-dropdowns.js +++ b/ui/app/components/account-dropdowns.js @@ -2,7 +2,7 @@ const Component = require('react').Component const PropTypes = require('react').PropTypes const h = require('react-hyperscript') const actions = require('../actions') -const genAccountLink = require('../../lib/account-link.js') +const genAccountLink = require('etherscan-link').createAccountLink const connect = require('react-redux').connect const Dropdown = require('./dropdown').Dropdown const DropdownMenuItem = require('./dropdown').DropdownMenuItem @@ -161,8 +161,6 @@ class AccountDropdowns extends Component { ) } - - renderAccountOptions () { const { actions } = this.props const { optionsMenuActive } = this.state @@ -297,6 +295,11 @@ AccountDropdowns.propTypes = { identities: PropTypes.objectOf(PropTypes.object), selected: PropTypes.string, keyrings: PropTypes.array, + actions: PropTypes.objectOf(PropTypes.func), + network: PropTypes.string, + style: PropTypes.object, + enableAccountOptions: PropTypes.bool, + enableAccountsSelector: PropTypes.bool, } const mapDispatchToProps = (dispatch) => { diff --git a/ui/app/components/bn-as-decimal-input.js b/ui/app/components/bn-as-decimal-input.js index f3ace4720..22e37602e 100644 --- a/ui/app/components/bn-as-decimal-input.js +++ b/ui/app/components/bn-as-decimal-input.js @@ -31,7 +31,9 @@ BnAsDecimalInput.prototype.render = function () { const suffix = props.suffix const style = props.style const valueString = value.toString(10) - const newValue = this.downsize(valueString, scale, precision) + const newMin = min && this.downsize(min.toString(10), scale) + const newMax = max && this.downsize(max.toString(10), scale) + const newValue = this.downsize(valueString, scale) return ( h('.flex-column', [ @@ -47,8 +49,8 @@ BnAsDecimalInput.prototype.render = function () { type: 'number', step: 'any', required: true, - min, - max, + min: newMin, + max: newMax, style: extend({ display: 'block', textAlign: 'right', @@ -128,15 +130,17 @@ BnAsDecimalInput.prototype.updateValidity = function (event) { } BnAsDecimalInput.prototype.constructWarning = function () { - const { name, min, max } = this.props + const { name, min, max, scale, suffix } = this.props + const newMin = min && this.downsize(min.toString(10), scale) + const newMax = max && this.downsize(max.toString(10), scale) let message = name ? name + ' ' : '' if (min && max) { - message += `must be greater than or equal to ${min} and less than or equal to ${max}.` + message += `must be greater than or equal to ${newMin} ${suffix} and less than or equal to ${newMax} ${suffix}.` } else if (min) { - message += `must be greater than or equal to ${min}.` + message += `must be greater than or equal to ${newMin} ${suffix}.` } else if (max) { - message += `must be less than or equal to ${max}.` + message += `must be less than or equal to ${newMax} ${suffix}.` } else { message += 'Invalid input.' } @@ -145,14 +149,17 @@ BnAsDecimalInput.prototype.constructWarning = function () { } -BnAsDecimalInput.prototype.downsize = function (number, scale, precision) { +BnAsDecimalInput.prototype.downsize = function (number, scale) { // if there is no scaling, simply return the number if (scale === 0) { return Number(number) } else { // if the scale is the same as the precision, account for this edge case. - var decimals = (scale === precision) ? -1 : scale - precision - return Number(number.slice(0, -scale) + '.' + number.slice(-scale, decimals)) + var adjustedNumber = number + while (adjustedNumber.length < scale) { + adjustedNumber = '0' + adjustedNumber + } + return Number(adjustedNumber.slice(0, -scale) + '.' + adjustedNumber.slice(-scale)) } } diff --git a/ui/app/components/dropdown.js b/ui/app/components/dropdown.js index 73710acc2..cdd864cc3 100644 --- a/ui/app/components/dropdown.js +++ b/ui/app/components/dropdown.js @@ -52,6 +52,9 @@ Dropdown.propTypes = { onClick: PropTypes.func.isRequired, children: PropTypes.node, style: PropTypes.object.isRequired, + onClickOutside: PropTypes.func, + innerStyle: PropTypes.object, + useCssTransition: PropTypes.bool, } class DropdownMenuItem extends Component { @@ -86,6 +89,7 @@ DropdownMenuItem.propTypes = { closeMenu: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired, children: PropTypes.node, + style: PropTypes.object, } module.exports = { diff --git a/ui/app/components/editable-label.js b/ui/app/components/editable-label.js index 167be7eaf..8a5c8954f 100644 --- a/ui/app/components/editable-label.js +++ b/ui/app/components/editable-label.js @@ -48,6 +48,7 @@ EditableLabel.prototype.saveIfEnter = function (event) { } EditableLabel.prototype.saveText = function () { + // eslint-disable-next-line react/no-find-dom-node var container = findDOMNode(this) var text = container.querySelector('.editable-label input').value var truncatedText = text.substring(0, 20) diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index 3a33ebf74..c85a23514 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -6,7 +6,7 @@ const debounce = require('debounce') const copyToClipboard = require('copy-to-clipboard') const ENS = require('ethjs-ens') const networkMap = require('ethjs-ens/lib/network-map.json') -const ensRE = /.+\.eth$/ +const ensRE = /.+\..+$/ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js index c754bc6ba..bb476ca7b 100644 --- a/ui/app/components/identicon.js +++ b/ui/app/components/identicon.js @@ -41,6 +41,7 @@ IdenticonComponent.prototype.componentDidMount = function () { if (!address) return + // eslint-disable-next-line react/no-find-dom-node var container = findDOMNode(this) var diameter = props.diameter || this.defaultDiameter @@ -56,6 +57,7 @@ IdenticonComponent.prototype.componentDidUpdate = function () { if (!address) return + // eslint-disable-next-line react/no-find-dom-node var container = findDOMNode(this) var children = container.children diff --git a/ui/app/components/menu-droppo.js b/ui/app/components/menu-droppo.js index 66ab18954..e6276f3b1 100644 --- a/ui/app/components/menu-droppo.js +++ b/ui/app/components/menu-droppo.js @@ -19,7 +19,7 @@ MenuDroppoComponent.prototype.render = function () { this.manageListeners() - let style = this.props.style || {} + const style = this.props.style || {} if (!('position' in style)) { style.position = 'fixed' } @@ -95,6 +95,7 @@ MenuDroppoComponent.prototype.componentDidMount = function () { if (this && document.body) { this.globalClickHandler = this.globalClickOccurred.bind(this) document.body.addEventListener('click', this.globalClickHandler) + // eslint-disable-next-line react/no-find-dom-node var container = findDOMNode(this) this.container = container } @@ -108,6 +109,7 @@ MenuDroppoComponent.prototype.componentWillUnmount = function () { MenuDroppoComponent.prototype.globalClickOccurred = function (event) { const target = event.target + // eslint-disable-next-line react/no-find-dom-node const container = findDOMNode(this) if (target !== container && diff --git a/ui/app/components/notice.js b/ui/app/components/notice.js index c26505193..09d461c7b 100644 --- a/ui/app/components/notice.js +++ b/ui/app/components/notice.js @@ -117,6 +117,7 @@ Notice.prototype.render = function () { } Notice.prototype.componentDidMount = function () { + // eslint-disable-next-line react/no-find-dom-node var node = findDOMNode(this) linker.setupListener(node) if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) { @@ -125,6 +126,7 @@ Notice.prototype.componentDidMount = function () { } Notice.prototype.componentWillUnmount = function () { + // eslint-disable-next-line react/no-find-dom-node var node = findDOMNode(this) linker.teardownListener(node) } diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index c3350fcc1..5b1b367c6 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -15,10 +15,9 @@ const addressSummary = util.addressSummary const nameForAddress = require('../../lib/contract-namer') const BNInput = require('./bn-as-decimal-input') -const MIN_GAS_PRICE_GWEI_BN = new BN(1) -const GWEI_FACTOR = new BN(1e9) -const MIN_GAS_PRICE_BN = MIN_GAS_PRICE_GWEI_BN.mul(GWEI_FACTOR) -const MIN_GAS_LIMIT_BN = new BN(21000) +// corresponds with 0.1 GWEI +const MIN_GAS_PRICE_BN = new BN('100000000') +const MIN_GAS_LIMIT_BN = new BN('21000') module.exports = PendingTx inherits(PendingTx, Component) @@ -175,7 +174,7 @@ PendingTx.prototype.render = function () { precision: 0, scale: 0, // The hard lower limit for gas. - min: MIN_GAS_LIMIT_BN.toString(10), + min: MIN_GAS_LIMIT_BN, max: safeGasLimit, suffix: 'UNITS', style: { @@ -200,7 +199,7 @@ PendingTx.prototype.render = function () { precision: 9, scale: 9, suffix: 'GWEI', - min: MIN_GAS_PRICE_GWEI_BN.toString(10), + min: MIN_GAS_PRICE_BN, style: { position: 'relative', top: '5px', diff --git a/ui/app/components/pending-typed-msg-details.js b/ui/app/components/pending-typed-msg-details.js new file mode 100644 index 000000000..b5fd29f71 --- /dev/null +++ b/ui/app/components/pending-typed-msg-details.js @@ -0,0 +1,59 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits + +const AccountPanel = require('./account-panel') +const TypedMessageRenderer = require('./typed-message-renderer') + +module.exports = PendingMsgDetails + +inherits(PendingMsgDetails, Component) +function PendingMsgDetails () { + Component.call(this) +} + +PendingMsgDetails.prototype.render = function () { + var state = this.props + var msgData = state.txData + + var msgParams = msgData.msgParams || {} + var address = msgParams.from || state.selectedAddress + var identity = state.identities[address] || { address: address } + var account = state.accounts[address] || { address: address } + + var { data } = msgParams + + return ( + h('div', { + key: msgData.id, + style: { + margin: '10px 20px', + }, + }, [ + + // account that will sign + h(AccountPanel, { + showFullAddress: true, + identity: identity, + account: account, + imageifyIdenticons: state.imageifyIdenticons, + }), + + // message data + h('div', { + style: { + height: '260px', + }, + }, [ + h('label.font-small', { style: { display: 'block' } }, 'YOU ARE SIGNING'), + h(TypedMessageRenderer, { + value: data, + style: { + height: '215px', + }, + }), + ]), + + ]) + ) +} diff --git a/ui/app/components/pending-typed-msg.js b/ui/app/components/pending-typed-msg.js new file mode 100644 index 000000000..f8926d0a3 --- /dev/null +++ b/ui/app/components/pending-typed-msg.js @@ -0,0 +1,46 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const PendingTxDetails = require('./pending-typed-msg-details') + +module.exports = PendingMsg + +inherits(PendingMsg, Component) +function PendingMsg () { + Component.call(this) +} + +PendingMsg.prototype.render = function () { + var state = this.props + var msgData = state.txData + + return ( + + h('div', { + key: msgData.id, + }, [ + + // header + h('h3', { + style: { + fontWeight: 'bold', + textAlign: 'center', + }, + }, 'Sign Message'), + + // message details + h(PendingTxDetails, state), + + // sign + cancel + h('.flex-row.flex-space-around', [ + h('button', { + onClick: state.cancelTypedMessage, + }, 'Cancel'), + h('button', { + onClick: state.signTypedMessage, + }, 'Sign'), + ]), + ]) + + ) +} diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index 901a4a956..c5993e3d3 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -130,9 +130,9 @@ ShapeshiftForm.prototype.renderMain = function () { alignItems: 'flex-start', }, }, [ - this.props.warning - ? this.props.warning - && h('span.error.flex-center', { + this.props.warning ? + this.props.warning && + h('span.error.flex-center', { style: { textAlign: 'center', width: '229px', diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/shift-list-item.js index 079f05e31..b555dee84 100644 --- a/ui/app/components/shift-list-item.js +++ b/ui/app/components/shift-list-item.js @@ -3,7 +3,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const connect = require('react-redux').connect const vreme = new (require('vreme'))() -const explorerLink = require('../../lib/explorer-link') +const explorerLink = require('etherscan-link').createExplorerLink const actions = require('../actions') const addressSummary = require('../util').addressSummary diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index a9961f47c..891d5e227 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -4,7 +4,7 @@ const inherits = require('util').inherits const EthBalance = require('./eth-balance') const addressSummary = require('../util').addressSummary -const explorerLink = require('../../lib/explorer-link') +const explorerLink = require('etherscan-link').createExplorerLink const CopyButton = require('./copyButton') const vreme = new (require('vreme'))() const Tooltip = require('./tooltip') diff --git a/ui/app/components/typed-message-renderer.js b/ui/app/components/typed-message-renderer.js new file mode 100644 index 000000000..d170d63b7 --- /dev/null +++ b/ui/app/components/typed-message-renderer.js @@ -0,0 +1,42 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const extend = require('xtend') + +module.exports = TypedMessageRenderer + +inherits(TypedMessageRenderer, Component) +function TypedMessageRenderer () { + Component.call(this) +} + +TypedMessageRenderer.prototype.render = function () { + const props = this.props + const { value, style } = props + const text = renderTypedData(value) + + const defaultStyle = extend({ + width: '315px', + maxHeight: '210px', + resize: 'none', + border: 'none', + background: 'white', + padding: '3px', + overflow: 'scroll', + }, style) + + return ( + h('div.font-small', { + style: defaultStyle, + }, text) + ) +} + +function renderTypedData (values) { + return values.map(function (value) { + return h('div', {}, [ + h('strong', {style: {display: 'block', fontWeight: 'bold'}}, String(value.name) + ':'), + h('div', {}, value.value), + ]) + }) +} diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 15fb9a59f..cb1afedfe 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -10,6 +10,7 @@ const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notific const PendingTx = require('./components/pending-tx') const PendingMsg = require('./components/pending-msg') const PendingPersonalMsg = require('./components/pending-personal-msg') +const PendingTypedMsg = require('./components/pending-typed-msg') const Loading = require('./components/loading') module.exports = connect(mapStateToProps)(ConfirmTxScreen) @@ -22,6 +23,7 @@ function mapStateToProps (state) { unapprovedTxs: state.metamask.unapprovedTxs, unapprovedMsgs: state.metamask.unapprovedMsgs, unapprovedPersonalMsgs: state.metamask.unapprovedPersonalMsgs, + unapprovedTypedMessages: state.metamask.unapprovedTypedMessages, index: state.appState.currentView.context, warning: state.appState.warning, network: state.metamask.network, @@ -41,9 +43,9 @@ function ConfirmTxScreen () { ConfirmTxScreen.prototype.render = function () { const props = this.props const { network, provider, unapprovedTxs, currentCurrency, computedBalances, - unapprovedMsgs, unapprovedPersonalMsgs, conversionRate, blockGasLimit } = props + unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, conversionRate, blockGasLimit } = props - var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) + var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network) var txData = unconfTxList[props.index] || {} var txParams = txData.params || {} @@ -112,8 +114,10 @@ ConfirmTxScreen.prototype.render = function () { cancelAllTransactions: this.cancelAllTransactions.bind(this, unconfTxList), signMessage: this.signMessage.bind(this, txData), signPersonalMessage: this.signPersonalMessage.bind(this, txData), + signTypedMessage: this.signTypedMessage.bind(this, txData), cancelMessage: this.cancelMessage.bind(this, txData), cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData), + cancelTypedMessage: this.cancelTypedMessage.bind(this, txData), }), ]) ) @@ -136,6 +140,9 @@ function currentTxView (opts) { } else if (type === 'personal_sign') { log.debug('rendering personal_sign message') return h(PendingPersonalMsg, opts) + } else if (type === 'eth_signTypedData') { + log.debug('rendering eth_signTypedData message') + return h(PendingTypedMsg, opts) } } } @@ -184,6 +191,14 @@ ConfirmTxScreen.prototype.signPersonalMessage = function (msgData, event) { this.props.dispatch(actions.signPersonalMsg(params)) } +ConfirmTxScreen.prototype.signTypedMessage = function (msgData, event) { + log.info('conf-tx.js: signing typed message') + var params = msgData.msgParams + params.metamaskId = msgData.id + this.stopPropagation(event) + this.props.dispatch(actions.signTypedMsg(params)) +} + ConfirmTxScreen.prototype.cancelMessage = function (msgData, event) { log.info('canceling message') this.stopPropagation(event) @@ -196,6 +211,12 @@ ConfirmTxScreen.prototype.cancelPersonalMessage = function (msgData, event) { this.props.dispatch(actions.cancelPersonalMsg(msgData)) } +ConfirmTxScreen.prototype.cancelTypedMessage = function (msgData, event) { + log.info('canceling typed message') + this.stopPropagation(event) + this.props.dispatch(actions.cancelTypedMsg(msgData)) +} + ConfirmTxScreen.prototype.goHome = function (event) { this.stopPropagation(event) this.props.dispatch(actions.goHome()) diff --git a/ui/app/config.js b/ui/app/config.js index 0fe232c07..c14fa1d28 100644 --- a/ui/app/config.js +++ b/ui/app/config.js @@ -113,7 +113,13 @@ ConfigScreen.prototype.render = function () { alignSelf: 'center', }, onClick (event) { - exportAsFile('MetaMask State Logs', window.logState()) + window.logStateString((err, result) => { + if (err) { + state.dispatch(actions.displayWarning('Error in retrieving state logs.')) + } else { + exportAsFile('MetaMask State Logs', result) + } + }) }, }, 'Download State Logs'), ]), diff --git a/ui/app/css/index.css b/ui/app/css/index.css index 49b432a1f..0630c4c12 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/index.css @@ -235,7 +235,8 @@ app sections /* unlock */ .error { - color: #E20202; + color: #f7861c; + margin-bottom: 9px; } .warning { diff --git a/ui/app/keychains/hd/create-vault-complete.js b/ui/app/keychains/hd/create-vault-complete.js index 745990351..5ab5d4c33 100644 --- a/ui/app/keychains/hd/create-vault-complete.js +++ b/ui/app/keychains/hd/create-vault-complete.js @@ -62,7 +62,8 @@ CreateVaultCompleteScreen.prototype.render = function () { }), h('button.primary', { - onClick: () => this.confirmSeedWords(), + onClick: () => this.confirmSeedWords() + .then(account => this.showAccountDetail(account)), style: { margin: '24px', fontSize: '0.9em', @@ -82,5 +83,9 @@ CreateVaultCompleteScreen.prototype.render = function () { } CreateVaultCompleteScreen.prototype.confirmSeedWords = function () { - this.props.dispatch(actions.confirmSeedWords()) + return this.props.dispatch(actions.confirmSeedWords()) +} + +CreateVaultCompleteScreen.prototype.showAccountDetail = function (account) { + return this.props.dispatch(actions.showAccountDetail(account)) } diff --git a/ui/app/reducers.js b/ui/app/reducers.js index 6a2f44534..05136f70f 100644 --- a/ui/app/reducers.js +++ b/ui/app/reducers.js @@ -1,4 +1,5 @@ const extend = require('xtend') +const copyToClipboard = require('copy-to-clipboard') // // Sub-Reducers take in the complete state and return their sub-state @@ -41,12 +42,33 @@ function rootReducer (state, action) { return state } -window.logState = function () { +window.logStateString = function (cb) { let state = window.METAMASK_CACHED_LOG_STATE const version = global.platform.getVersion() - state.version = version - let stateString = JSON.stringify(state, removeSeedWords, 2) - return stateString + const browser = window.navigator.userAgent + return global.platform.getPlatformInfo((err, platform) => { + if (err) { + return cb(err) + } + state.version = version + state.platform = platform + state.browser = browser + let stateString = JSON.stringify(state, removeSeedWords, 2) + return cb(null, stateString) + }) +} + +window.logState = function (toClipboard) { + return window.logStateString((err, result) => { + if (err) { + console.error(err.message) + } else if (toClipboard) { + copyToClipboard(result) + console.log('State log copied') + } else { + console.log(result) + } + }) } function removeSeedWords (key, value) { diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 3a98d53a9..6f08c6dc4 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -494,6 +494,16 @@ function reduceApp (state, action) { }, }) + case actions.ONBOARDING_BUY_ETH_VIEW: + return extend(appState, { + transForward: true, + currentView: { + name: 'onboardingBuyEth', + context: appState.currentView.name, + }, + identity: state.metamask.identities[action.value], + }) + case actions.COINBASE_SUBVIEW: return extend(appState, { buyView: { @@ -574,9 +584,9 @@ function checkUnconfActions (state) { function getUnconfActionList (state) { const { unapprovedTxs, unapprovedMsgs, - unapprovedPersonalMsgs, network } = state.metamask + unapprovedPersonalMsgs, unapprovedTypedMessages, network } = state.metamask - const unconfActionList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) + const unconfActionList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network) return unconfActionList } diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index e0c416c2d..85ac3e201 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -1,5 +1,6 @@ const extend = require('xtend') const actions = require('../actions') +const MetamascaraPlatform = require('../../../app/scripts/platforms/window') module.exports = reduceMetamask @@ -10,6 +11,7 @@ function reduceMetamask (state, action) { var metamaskState = extend({ isInitialized: false, isUnlocked: false, + isMascara: window.platform instanceof MetamascaraPlatform, rpcTarget: 'https://rawtestrpc.metamask.io/', identities: {}, unapprovedTxs: {}, @@ -17,6 +19,8 @@ function reduceMetamask (state, action) { lastUnreadNotice: undefined, frequentRpcList: [], addressBook: [], + tokenExchangeRates: {}, + coinOptions: {}, }, state.metamask) switch (action.type) { @@ -130,6 +134,25 @@ function reduceMetamask (state, action) { conversionDate: action.value.conversionDate, }) + case actions.PAIR_UPDATE: + const { value: { marketinfo: pairMarketInfo } } = action + return extend(metamaskState, { + tokenExchangeRates: { + ...metamaskState.tokenExchangeRates, + [pairMarketInfo.pair]: pairMarketInfo, + }, + }) + + case actions.SHAPESHIFT_SUBVIEW: + const { value: { marketinfo, coinOptions } } = action + return extend(metamaskState, { + tokenExchangeRates: { + ...metamaskState.tokenExchangeRates, + [marketinfo.pair]: marketinfo, + }, + coinOptions, + }) + default: return metamaskState @@ -9,6 +9,7 @@ var cssFiles = { 'lib.css': fs.readFileSync(path.join(__dirname, '/app/css/lib.css'), 'utf8'), 'index.css': fs.readFileSync(path.join(__dirname, '/app/css/index.css'), 'utf8'), 'transitions.css': fs.readFileSync(path.join(__dirname, '/app/css/transitions.css'), 'utf8'), + 'first-time.css': fs.readFileSync(path.join(__dirname, '../mascara/src/app/first-time/index.css'), 'utf8'), 'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'), 'react-css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'), } diff --git a/ui/index.js b/ui/index.js index a729138d3..ae05cbe67 100644 --- a/ui/index.js +++ b/ui/index.js @@ -37,7 +37,7 @@ function startApp (metamaskState, accountManager, opts) { }) // if unconfirmed txs, start on txConf page - const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.network) + const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.unapprovedTypedMessages, metamaskState.network) if (unapprovedTxsAll.length > 0) { store.dispatch(actions.showConfTxPage()) } diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js deleted file mode 100644 index 037d990fa..000000000 --- a/ui/lib/account-link.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = function (address, network) { - const net = parseInt(network) - let link - switch (net) { - case 1: // main net - link = `https://etherscan.io/address/${address}` - break - case 2: // morden test net - link = `https://morden.etherscan.io/address/${address}` - break - case 3: // ropsten test net - link = `https://ropsten.etherscan.io/address/${address}` - break - case 4: // rinkeby test net - link = `https://rinkeby.etherscan.io/address/${address}` - break - case 42: // kovan test net - link = `https://kovan.etherscan.io/address/${address}` - break - default: - link = '' - break - } - - return link -} diff --git a/ui/lib/explorer-link.js b/ui/lib/explorer-link.js deleted file mode 100644 index 3b82ecd5f..000000000 --- a/ui/lib/explorer-link.js +++ /dev/null @@ -1,6 +0,0 @@ -const prefixForNetwork = require('./etherscan-prefix-for-network') - -module.exports = function (hash, network) { - const prefix = prefixForNetwork(network) - return `http://${prefix}etherscan.io/tx/${hash}` -} diff --git a/ui/lib/tx-helper.js b/ui/lib/tx-helper.js index 5def23e51..de3f00d2d 100644 --- a/ui/lib/tx-helper.js +++ b/ui/lib/tx-helper.js @@ -1,17 +1,24 @@ const valuesFor = require('../app/util').valuesFor -module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, network) { +module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, typedMessages, network) { log.debug('tx-helper called with params:') - log.debug({ unapprovedTxs, unapprovedMsgs, personalMsgs, network }) + log.debug({ unapprovedTxs, unapprovedMsgs, personalMsgs, typedMessages, network }) const txValues = network ? valuesFor(unapprovedTxs).filter(txMeta => txMeta.metamaskNetworkId === network) : valuesFor(unapprovedTxs) log.debug(`tx helper found ${txValues.length} unapproved txs`) + const msgValues = valuesFor(unapprovedMsgs) log.debug(`tx helper found ${msgValues.length} unsigned messages`) let allValues = txValues.concat(msgValues) + const personalValues = valuesFor(personalMsgs) log.debug(`tx helper found ${personalValues.length} unsigned personal messages`) allValues = allValues.concat(personalValues) + + const typedValues = valuesFor(typedMessages) + log.debug(`tx helper found ${typedValues.length} unsigned typed messages`) + allValues = allValues.concat(typedValues) + allValues = allValues.sort((a, b) => { return a.time > b.time }) |