diff options
Diffstat (limited to 'ui/app/pages/confirm-transaction')
4 files changed, 424 insertions, 0 deletions
diff --git a/ui/app/pages/confirm-transaction/conf-tx.js b/ui/app/pages/confirm-transaction/conf-tx.js new file mode 100644 index 000000000..f9af6624e --- /dev/null +++ b/ui/app/pages/confirm-transaction/conf-tx.js @@ -0,0 +1,225 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const connect = require('react-redux').connect +const { withRouter } = require('react-router-dom') +const { compose } = require('recompose') +const actions = require('../../store/actions') +const txHelper = require('../../../lib/tx-helper') +const log = require('loglevel') +const R = require('ramda') + +const SignatureRequest = require('../../components/app/signature-request') +const Loading = require('../../components/ui/loading-screen') +const { DEFAULT_ROUTE } = require('../../helpers/constants/routes') +const { getMetaMaskAccounts } = require('../../selectors/selectors') + +module.exports = compose( + withRouter, + connect(mapStateToProps) +)(ConfirmTxScreen) + +function mapStateToProps (state) { + const { metamask } = state + const { + unapprovedMsgCount, + unapprovedPersonalMsgCount, + unapprovedTypedMessagesCount, + } = metamask + + return { + identities: state.metamask.identities, + accounts: getMetaMaskAccounts(state), + selectedAddress: state.metamask.selectedAddress, + 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, + provider: state.metamask.provider, + conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, + blockGasLimit: state.metamask.currentBlockGasLimit, + computedBalances: state.metamask.computedBalances, + unapprovedMsgCount, + unapprovedPersonalMsgCount, + unapprovedTypedMessagesCount, + send: state.metamask.send, + selectedAddressTxList: state.metamask.selectedAddressTxList, + } +} + +inherits(ConfirmTxScreen, Component) +function ConfirmTxScreen () { + Component.call(this) +} + +ConfirmTxScreen.prototype.getUnapprovedMessagesTotal = function () { + const { + unapprovedMsgCount = 0, + unapprovedPersonalMsgCount = 0, + unapprovedTypedMessagesCount = 0, + } = this.props + + return unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount +} + +ConfirmTxScreen.prototype.componentDidMount = function () { + const { + unapprovedTxs = {}, + network, + send, + } = this.props + const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network) + + if (unconfTxList.length === 0 && !send.to && this.getUnapprovedMessagesTotal() === 0) { + this.props.history.push(DEFAULT_ROUTE) + } +} + +ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) { + const { + unapprovedTxs = {}, + network, + selectedAddressTxList, + send, + history, + match: { params: { id: transactionId } = {} }, + } = this.props + + let prevTx + + if (transactionId) { + prevTx = R.find(({ id }) => id + '' === transactionId)(selectedAddressTxList) + } else { + const { index: prevIndex, unapprovedTxs: prevUnapprovedTxs } = prevProps + const prevUnconfTxList = txHelper(prevUnapprovedTxs, {}, {}, {}, network) + const prevTxData = prevUnconfTxList[prevIndex] || {} + prevTx = selectedAddressTxList.find(({ id }) => id === prevTxData.id) || {} + } + + const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network) + + if (prevTx && prevTx.status === 'dropped') { + this.props.dispatch(actions.showModal({ + name: 'TRANSACTION_CONFIRMED', + onSubmit: () => history.push(DEFAULT_ROUTE), + })) + + return + } + + if (unconfTxList.length === 0 && !send.to && this.getUnapprovedMessagesTotal() === 0) { + this.props.history.push(DEFAULT_ROUTE) + } +} + +ConfirmTxScreen.prototype.getTxData = function () { + const { + network, + index, + unapprovedTxs, + unapprovedMsgs, + unapprovedPersonalMsgs, + unapprovedTypedMessages, + match: { params: { id: transactionId } = {} }, + } = this.props + + const unconfTxList = txHelper( + unapprovedTxs, + unapprovedMsgs, + unapprovedPersonalMsgs, + unapprovedTypedMessages, + network + ) + + log.info(`rendering a combined ${unconfTxList.length} unconf msgs & txs`) + + return transactionId + ? R.find(({ id }) => id + '' === transactionId)(unconfTxList) + : unconfTxList[index] +} + +ConfirmTxScreen.prototype.render = function () { + const props = this.props + const { + currentCurrency, + conversionRate, + blockGasLimit, + } = props + + var txData = this.getTxData() || {} + const { msgParams } = txData + log.debug('msgParams detected, rendering pending msg') + + return msgParams + ? h(SignatureRequest, { + // Properties + txData: txData, + key: txData.id, + selectedAddress: props.selectedAddress, + accounts: props.accounts, + identities: props.identities, + conversionRate, + currentCurrency, + blockGasLimit, + // Actions + 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), + }) + : h(Loading) +} + +ConfirmTxScreen.prototype.signMessage = function (msgData, event) { + log.info('conf-tx.js: signing message') + var params = msgData.msgParams + params.metamaskId = msgData.id + this.stopPropagation(event) + return this.props.dispatch(actions.signMsg(params)) +} + +ConfirmTxScreen.prototype.stopPropagation = function (event) { + if (event.stopPropagation) { + event.stopPropagation() + } +} + +ConfirmTxScreen.prototype.signPersonalMessage = function (msgData, event) { + log.info('conf-tx.js: signing personal message') + var params = msgData.msgParams + params.metamaskId = msgData.id + this.stopPropagation(event) + return 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) + return this.props.dispatch(actions.signTypedMsg(params)) +} + +ConfirmTxScreen.prototype.cancelMessage = function (msgData, event) { + log.info('canceling message') + this.stopPropagation(event) + return this.props.dispatch(actions.cancelMsg(msgData)) +} + +ConfirmTxScreen.prototype.cancelPersonalMessage = function (msgData, event) { + log.info('canceling personal message') + this.stopPropagation(event) + return this.props.dispatch(actions.cancelPersonalMsg(msgData)) +} + +ConfirmTxScreen.prototype.cancelTypedMessage = function (msgData, event) { + log.info('canceling typed message') + this.stopPropagation(event) + return this.props.dispatch(actions.cancelTypedMsg(msgData)) +} diff --git a/ui/app/pages/confirm-transaction/confirm-transaction.component.js b/ui/app/pages/confirm-transaction/confirm-transaction.component.js new file mode 100644 index 000000000..35b8dc5aa --- /dev/null +++ b/ui/app/pages/confirm-transaction/confirm-transaction.component.js @@ -0,0 +1,160 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import { Switch, Route } from 'react-router-dom' +import Loading from '../../components/ui/loading-screen' +import ConfirmTransactionSwitch from '../confirm-transaction-switch' +import ConfirmTransactionBase from '../confirm-transaction-base' +import ConfirmSendEther from '../confirm-send-ether' +import ConfirmSendToken from '../confirm-send-token' +import ConfirmDeployContract from '../confirm-deploy-contract' +import ConfirmApprove from '../confirm-approve' +import ConfirmTokenTransactionBase from '../confirm-token-transaction-base' +import ConfTx from './conf-tx' +import { + DEFAULT_ROUTE, + CONFIRM_TRANSACTION_ROUTE, + CONFIRM_DEPLOY_CONTRACT_PATH, + CONFIRM_SEND_ETHER_PATH, + CONFIRM_SEND_TOKEN_PATH, + CONFIRM_APPROVE_PATH, + CONFIRM_TRANSFER_FROM_PATH, + CONFIRM_TOKEN_METHOD_PATH, + SIGNATURE_REQUEST_PATH, +} from '../../helpers/constants/routes' + +export default class ConfirmTransaction extends Component { + static propTypes = { + history: PropTypes.object.isRequired, + totalUnapprovedCount: PropTypes.number.isRequired, + match: PropTypes.object, + send: PropTypes.object, + unconfirmedTransactions: PropTypes.array, + setTransactionToConfirm: PropTypes.func, + confirmTransaction: PropTypes.object, + clearConfirmTransaction: PropTypes.func, + fetchBasicGasAndTimeEstimates: PropTypes.func, + } + + getParamsTransactionId () { + const { match: { params: { id } = {} } } = this.props + return id || null + } + + componentDidMount () { + const { + totalUnapprovedCount = 0, + send = {}, + history, + confirmTransaction: { txData: { id: transactionId } = {} }, + fetchBasicGasAndTimeEstimates, + } = this.props + + if (!totalUnapprovedCount && !send.to) { + history.replace(DEFAULT_ROUTE) + return + } + + if (!transactionId) { + fetchBasicGasAndTimeEstimates() + this.setTransactionToConfirm() + } + } + + componentDidUpdate () { + const { + setTransactionToConfirm, + confirmTransaction: { txData: { id: transactionId } = {} }, + clearConfirmTransaction, + } = this.props + const paramsTransactionId = this.getParamsTransactionId() + + if (paramsTransactionId && transactionId && paramsTransactionId !== transactionId + '') { + clearConfirmTransaction() + setTransactionToConfirm(paramsTransactionId) + return + } + + if (!transactionId) { + this.setTransactionToConfirm() + } + } + + setTransactionToConfirm () { + const { + history, + unconfirmedTransactions, + setTransactionToConfirm, + } = this.props + const paramsTransactionId = this.getParamsTransactionId() + + if (paramsTransactionId) { + // Check to make sure params ID is valid + const tx = unconfirmedTransactions.find(({ id }) => id + '' === paramsTransactionId) + + if (!tx) { + history.replace(DEFAULT_ROUTE) + } else { + setTransactionToConfirm(paramsTransactionId) + } + } else if (unconfirmedTransactions.length) { + const totalUnconfirmed = unconfirmedTransactions.length + const transaction = unconfirmedTransactions[totalUnconfirmed - 1] + const { id: transactionId, loadingDefaults } = transaction + + if (!loadingDefaults) { + setTransactionToConfirm(transactionId) + } + } + } + + render () { + const { confirmTransaction: { txData: { id } } = {} } = this.props + const paramsTransactionId = this.getParamsTransactionId() + + // Show routes when state.confirmTransaction has been set and when either the ID in the params + // isn't specified or is specified and matches the ID in state.confirmTransaction in order to + // support URLs of /confirm-transaction or /confirm-transaction/<transactionId> + return id && (!paramsTransactionId || paramsTransactionId === id + '') + ? ( + <Switch> + <Route + exact + path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_DEPLOY_CONTRACT_PATH}`} + component={ConfirmDeployContract} + /> + <Route + exact + path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_TOKEN_METHOD_PATH}`} + component={ConfirmTransactionBase} + /> + <Route + exact + path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_SEND_ETHER_PATH}`} + component={ConfirmSendEther} + /> + <Route + exact + path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_SEND_TOKEN_PATH}`} + component={ConfirmSendToken} + /> + <Route + exact + path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_APPROVE_PATH}`} + component={ConfirmApprove} + /> + <Route + exact + path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_TRANSFER_FROM_PATH}`} + component={ConfirmTokenTransactionBase} + /> + <Route + exact + path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${SIGNATURE_REQUEST_PATH}`} + component={ConfTx} + /> + <Route path="*" component={ConfirmTransactionSwitch} /> + </Switch> + ) + : <Loading /> + } +} diff --git a/ui/app/pages/confirm-transaction/confirm-transaction.container.js b/ui/app/pages/confirm-transaction/confirm-transaction.container.js new file mode 100644 index 000000000..2dd5e833e --- /dev/null +++ b/ui/app/pages/confirm-transaction/confirm-transaction.container.js @@ -0,0 +1,37 @@ +import { connect } from 'react-redux' +import { compose } from 'recompose' +import { withRouter } from 'react-router-dom' +import { + setTransactionToConfirm, + clearConfirmTransaction, +} from '../../ducks/confirm-transaction/confirm-transaction.duck' +import { + fetchBasicGasAndTimeEstimates, +} from '../../ducks/gas/gas.duck' +import ConfirmTransaction from './confirm-transaction.component' +import { getTotalUnapprovedCount } from '../../selectors/selectors' +import { unconfirmedTransactionsListSelector } from '../../selectors/confirm-transaction' + +const mapStateToProps = state => { + const { metamask: { send }, confirmTransaction } = state + + return { + totalUnapprovedCount: getTotalUnapprovedCount(state), + send, + confirmTransaction, + unconfirmedTransactions: unconfirmedTransactionsListSelector(state), + } +} + +const mapDispatchToProps = dispatch => { + return { + setTransactionToConfirm: transactionId => dispatch(setTransactionToConfirm(transactionId)), + clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), + fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()), + } +} + +export default compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps), +)(ConfirmTransaction) diff --git a/ui/app/pages/confirm-transaction/index.js b/ui/app/pages/confirm-transaction/index.js new file mode 100644 index 000000000..4bf42d85c --- /dev/null +++ b/ui/app/pages/confirm-transaction/index.js @@ -0,0 +1,2 @@ +import ConfirmTransaction from './confirm-transaction.container' +module.exports = ConfirmTransaction |